ΕΙΣΑΓΩΓΗ
Στο σημερινό δωρεάν μάθημα Java θα μιλήσουμε για την κληρονομικότητα (inheritance) και θα εξηγήσουμε γιατί είναι το πιο σημαντικό στοιχείο του αντικειμενοστραφή προγραμματισμού (Object-Oriented Programming).
Αν και η έννοια είναι πολύ απλή, τόσο στην κατανόηση της όσο και στην υλοποίηση της, δυσκολεύει τους αρχάριους προγραμματιστές γιατί προσπαθούν να την μάθουν μέσα από πολύπλοκα παραδείγματα. Για αυτόν ακριβώς το λόγο, θα αναλύσουμε ένα απλό παράδειγμα από την αρχή ως το τέλος, όπως θα έκανε κάποιος που ακόμα δεν έχει πολλή εμπειρία στον προγραμματισμό.
PROGRAMMING IN JAVA WITHOUT INHERITANCE
Πριν
ξεκινήσουμε, πρέπει να εξηγήσουμε το σενάριο με το οποίο θα έχουμε σαν βάση της
συζήτησης μας. Σε προηγούμενο δωρεάν μάθημα Java είχαμε δημιουργήσει την κλάση Employee
για να περιγράψουμε προγραμματιστικά τον εργαζόμενο μιας εταιρείας. Επίσης,
είχαμε προσθέσει στην κλάση την έννοια του encapsulation (αλλάζοντας τους access specifiers
από public σε
private
μπροστά από τις instance μεταβλητές) όπως και δημιουργήσαμε getter και setter μεθόδους για να έχουμε πρόσβαση στις instance
μεταβλητές. Τέλος, είχαμε δημιουργήσει κάποιες μεθόδους δίνοντας ιδιότητες και
δυνατότητες στο αντικείμενο. Θα έχουμε λοιπόν αυτήν την απλή κλάση Employee
σαν βάση της συζήτησης μας μόνο που θα μικρύνουμε τον κώδικα χρησιμοποιώντας public instance
μεταβλητές οπότε δεν χρειάζονται οι getter και setter μέθοδοι. Θα κρατήσουμε όμως δύο
μεθόδους που θα δώσουν κάποιες ιδιότητες στο αντικείμενο. Ας δούμε λοιπόν τον
απλοποιημένο κώδικα της κλάσης Employee.
Employee.java
Για να έχουμε
ένα ολοκληρωμένο πρόγραμμα, ας γράψουμε και μια κλάση EmployeeDemo
μέσα από την οποία δημιουργούμε αντικείμενα είδος Employee
και καλούμε τις μεταβλητές του για να ορίσουμε κάποιες τιμές.
EmployeeDemo.java
Output
The employee number 100 is a team
member of the Development department
Τώρα που
έχουμε την πιο απλή του προγράμματός μας, ας δούμε ποιες είναι οι καινούργιες
απαιτήσεις της εφαρμογής μας. Η εταιρεία, για την οποία δημιουργούμε το
πρόγραμμα, μας ενημέρωσε ότι υπάρχουν τουλάχιστον δύο κατηγορίες εργαζόμενων –
εκείνοι που είναι πλήρης απασχόλησης και οι εξωτερικοί συνεργάτες. Οι πρώτοι
έχουν σταθερό μηνιαίο μισθό, ενώ οι δεύτεροι πληρώνονται με τις ώρες που έχουν
δουλέψει. Αν μάλιστα ξεπεράσουν τις 40 ώρες τότε πληρώνονται και υπερωρίες.
Εκτός από τον υπολογισμό του μισθού που είναι διαφορετικός για κάθε κατηγορία,
οι υπόλοιπες πληροφορίες (name, department, AFM, κτλ) θα πρέπει να υπάρχουν για
όλους τους εργαζόμενους.
Με βάση
τα όσα γνωρίζετε μέχρι τώρα, και από την ανάλυση του προβλήματος, θα πρέπει να
δημιουργήσουμε δύο ακόμα κλάσεις – μια κλάση που περιγράφει τους σταθερούς
εργαζόμενους της εταιρείας και μια ακόμα κλάση για τους εξωτερικούς συνεργάτες.
Ας ονομάσουμε την πρώτη κλάση Salary και την δεύτερη Outsourcing. Aς γράψουμε τον κώδικα για τις
καινούργιες κλάσεις που προέκυψαν.
Salary.java
Outsourcing.java
Αν παρατηρήσετε
προσεκτικά, θα δείτε ότι η αρχική μας κλάση Employee, με την Salary και την Outsourcing
έχουν πολλά κοινά στοιχεία. Στην ουσία επαναλαμβάνουμε πολλές φορές τον ίδιο
κώδικα. Αν συγκρίνουμε την κλάση Employee και την κλάση Salary θα
δείτε ότι είναι ο ίδιος ακριβώς κώδικα. Ενώ αν συγκρίνετε την Employee με
την Outsourcing θα
δείτε ότι εκτός από δύο έξτρα instance μεταβλητές που προστέθηκαν ( hourlyRate και hoursWorked), όλες οι άλλες μεταβλητές είναι
ακριβώς οι ίδιες όπως και η mailCheck( ) μέθοδο. Η μέθοδος payment( )
στην κλάση Outsourcing
χρησιμοποιεί έναν απλό αλγόριθμό για να υπολογίσει την πληρωμή του εργαζόμενου
ανάλογα με τις ώρες εργασίας του. Αυτά τα πολλά κοινά στοιχεία ανάμεσα στις
κλάσεις δεν είναι βασικά μια απλή παρατήρηση μόνο αλλά και ένα σοβαρό πρόβλημα.
Αν χρειαστεί αργότερα να προσθέσετε μια ακόμα instance μεταβλητή ή να αλλάξετε τον κώδικα της mailcheck( )
τότε θα πρέπει να το κάνετε για όλες τις κλάσεις. Θα πρέπει να ξοδευτεί άσκοπα
πολύ χρόνος και σίγουρα μπορεί να ξεχάσετε κάποια αλλαγή που θα προκαλέσει
πρόβλημα στον κώδικα. Οπότε, πως προσεγγίζουμε μια τέτοια κατάσταση? Πριν
απαντήσουμε ας δούμε με την χρήση του UML πως απεικονίζονται οι κλάσεις μας μέχρι
τώρα.
Το όλο
μυστικό είναι η παρατήρηση που μόλις κάναμε, δηλαδή ο εντοπισμός κοινών
στοιχείων. Αυτό που θα μπορούσαμε θεωρητικά να κάνουμε είναι να βάλουμε αυτά τα
κοινά στοιχεία σε μια κλάση έτσι ώστε οποιαδήποτε άλλη κλάση να τα κληρονομεί
χωρίς να χρειάζεται να τα ξαναγράψει. Η κάθε καινούργια κλάση που δημιουργείται
απλά θα προσθέτει τα καινούργια στοιχεία που χρειάζεται. Αυτή η θεωρία πρακτικά
και προγραμματιστικά ονομάζεται inheritance και υποστηρίζεται από την java αν
προσθέσουμε την λέξη extends στην κλάση. Αν για παράδειγμα η
Salary
κληρονομεί από την Employee τότε θα γράψουμε:
public class Salary extends Employee
Με αυτή
την πράξη, αν κάνουμε αντικείμενο από την κλάση Salary θα δούμε ότι το αντικείμενο από
την Salary περιέχει και τα στοιχεία του Employee και του
Salary, ενώ
ένα αντικείμενο από την κλάση Employee θα περιέχει στοιχεία μόνο από
την κλάση Employee. Ας δούμε τώρα στην πράξη πως
μπορούμε να αλλάξουμε τον κώδικα των κλάσεων έτσι ώστε να ακολουθήσουμε την
λογική της κληρονομικότητας.
Η αρχή
πάντα γίνεται από την πιο γενική κλάση από την οποία θα κληρονομήσουν όλες οι
υπόλοιπες κλάσεις. Στο δικό μας παράδειγμα αυτή η κλάση είναι η Employee. Όλες
οι instance μεταβλητές της κλάσης Employee, εκτός
από την salary, και η μέθοδος mailCheck( ) πρέπει να υπάρχουν σε όλες τις κλάσεις, οπότε λογικά
μπορούμε να τα αφήσουμε στην κλάση Employee. Η μέθοδος payment( ),
μαζί με την instance μεταβλητή salary δεν
έχει νόημα να παραμείνει στην γενική κλάση Employee αφού μας ενδιαφέρει να ξέρουμε
πιο συγκεκριμένα αν είναι Salary ή Outsourcing οπότε δεν θα συμπεριλάβουμε την payment( )
μέθοδο στην κλάση Employee ούτε και την instance
μεταβλητή salary. Ο
τελικός κώδικας της κλάσης Employee θα είναι:
Employee.java
Η κλάση Salary
χρειάζεται όλα τα στοιχεία που περιέχει η κλάση Employee και πρέπει να προσθέσει και μια
instance
μεταβλητή salary η οποία θα χρησιμοποιηθεί στην
μέθοδο payment( ) που υπολογίζει τον μισθό του
εργαζόμενου ανά εβδομάδα. Η κλάση της Salary, εφόσον κληρονομεί από την
κλάση Employee, απλοποιείται και μετατρέπεται
στον εξής κώδικα:
Salary.java
Τέλος,
ακολουθώντας ακριβώς την ίδια λογική όπως κάναμε με την κλάση Salary, η
κλάση Outsourcing θα περιέχει και εκείνη μια payment( )
μέθοδο που όμως θα περιέχει διαφορετικό κώδικα ο οποίος θα υπολογίζει τον μισθό
του εργαζόμενου ανάλογα με τις ώρες που εργάστηκε. Η συγκεκριμένη κλάση θα
χρειαστεί δύο έξτρα instance μεταβλητές να οριστούν για να
μπορέσουμε να κάνουμε σωστά τους υπολογισμούς. Αυτές οι μεταβλητές είναι η hourlyRate και η hoursWorked. Ο κώδικας της κλάσης Outsourcing αλλάζει στον εξής:
Outsourcing.java
Πως έχει
αλλάξει τώρα το UML διάγραμμα μας?
Τώρα
είναι ώρα να δημιουργήσουμε μερικά αντικείμενα μέσα από την EmployeeDemo για να δούμε τι ακριβώς έχουμε καταφέρει. Ας δούμε πρώτα
την δημιουργία ενός αντικειμένου είδος Employee
EmployeeDemo.java
Output
Mailing a check to Michail
The name of the employee is Michail
Δημιουργήσαμε
ένα αντικείμενο e1 (στην ουσία είναι reference
αλλά καταλαβαίνετε τι εννοώ) και καλέσαμε την instance μεταβλητή για να δώσουμε ένα όνομα και
την μέθοδο mailCheck( )
για να εκτυπώσουμε το μήνυμα ότι στείλαμε ένα τσεκ στον εργαζόμενο. Έχει όμως
νόημα να κάνουμε αντικείμενο από την κλάση Employee? Όχι δεν έχει, γιατί είναι πολύ γενική
και δεν μας επιτρέπει να κάνουμε υπολογισμό μισθού. Για αυτό ακριβώς τον λόγο,
σε μελλοντική ενότητα δεν θα επιτρέπουμε να παράγονται αντικείμενα είδος Employee.
Όμως, για λόγους σύγκρισης, ας δούμε τι χαρακτηριστικά και ιδιότητες έχει το
αντικείμενο e1.
Βλέπουμε
τις τέσσερις μεταβλητές και την mailCheck( ) μέθοδο. Ας κάνουμε τώρα ένα
αντικείμενο είδος Salary.
EmployeeDemo.java
Output
Mailing a check to Michail
Your weekly payment is:
576.9230769230769
Εδώ, λόγω της
κληρονομικότητας, το αντικείμενο s1 έχει όλες τις ιδιότητες του Employee
και προσθέτει και την ιδιότητα του υπολογισμού του μισθού. Έχει περισσότερο
νόημα να κάνουμε αντικείμενο από την Salary γιατί το αντικείμενο μας θα είναι πιο
ολοκληρωμένο όσον αφορά τις πληροφορίες του. Μπορούμε να δούμε όλα τα
στοιχεία που έχει κληρονομήσει.
Με την ίδια ακριβώς λογική θα
ήταν πιο χρήσιμο ένα αντικείμενο είδος Outsourcing παρά ένα αντικείμενο Employee.
EmployeeDemo.java
Output
Mailing a check to Michail
Your weekly payment is:
576.9230769230769
Mailing a check to John
Your payment is: 890.0
Αν δούμε όλες
τις ιδιότητες και χαρακτηριστικά που έχει το αντικείμενο out1 τότε θα δείτε
ότι περιέχει όλα όσα περιέχει η Employee συν δύο έξτρα instance
μεταβλητές και την μέθοδο payment( ).
Νομίζω,
τώρα που φτάσαμε στο τέλος της ενότητας, ότι η έννοια της κληρονομικότητας (inheritance) σας είναι ξεκάθαρη και δεν σας μπερδεύει πια.
Πριν
κλείσουμε πρέπει να αναφέρουμε τον αυστηρό κανόνα με τον οποίο μας περιορίζει η
java όταν
χρησιμοποιούμε κληρονομικότητα – μπορούμε
να κληρονομήσουμε μόνο από μια άλλη κλάση. Δεν μπορεί δηλαδή η κλάση Salary να
κληρονομήσει και από την Employee και από την Outsourcing ταυτόχρονα. Αυτό δεν επιτρέπεται. Θα δούμε όμως σε
μελλοντική ενότητα πως μπορούμε να ξεπεράσουμε αυτό το όριο.
Μην ξεχάσετε να κάνετε ένα μικρό donation έτσι ώστε αυτό το blog να μεγαλώσει ακόμα πιο πολύ και να έχει περισσότερες δυνατότητες στην online παράδοση δωρεάν μαθημάτων.
full-width
1 Comments
Thank you
ReplyDeleteΗ γνώμη σας είναι σημαντική.