Κατά την εκμάθηση του αντικειμενοστραφούς προγραμματισμού και αφού κατακτήσει κανείς τα βασικά στοιχεία των αντικειμένων, των πεδίων και των μεθόδων, αφιερώνει τον περισσότερο χρόνο του στην κληρονομικότητα. Κληρονομικότητα σημαίνει ότι αποκτούμε κάποιο μέρος της υλοποίησης από μια βασική κλάση. Απλά πρέπει να δημιουργήσετε μια υποκλάση μιας βασικής κλάσης προκειμένου να κληρονομήσετε κάθε μη ιδιωτικό πεδίο και μέθοδο.
Ένα αυτοκίνητο και ένα αεροπλάνο είναι οχήματα, οπότε είναι προφανές ότι και οι δύο αυτές κλάσεις θα πρέπει να επεκταθούν από την κοινή βασική τους κλάση που ονομάζεται Vehicle. Αυτό είναι ένα τυπικό ακαδημαϊκό παράδειγμα, αλλά ενώ αποφασίζουμε για τη δέσμευση αυτών των κλάσεων με τη σχέση κληρονομικότητας, θα πρέπει να γνωρίζουμε ορισμένες συνέπειες.
Σχ. 1 Υλοποίηση της σχέσης κληρονομικότητας.
Σε αυτή την περίπτωση οι κλάσεις είναι στενά συνδεδεμένες μεταξύ τους - αυτό σημαίνει ότι οι αλλαγές στη συμπεριφορά κάθε κλάσης μπορούν να επιτευχθούν με αλλαγές στη βασική κλάση κωδικός. Αυτό μπορεί να είναι τόσο πλεονέκτημα όσο και μειονέκτημα - εξαρτάται από το είδος της συμπεριφοράς που περιμένουμε. Αν η κληρονομικότητα εφαρμοστεί σε λάθος χρόνο, η διαδικασία προσθήκης μιας νέας συνάρτησης μπορεί να αντιμετωπίσει κάποιες δυσκολίες στην υλοποίηση, επειδή δεν θα ταιριάζει στο μοντέλο κλάσης που έχει δημιουργηθεί. Θα πρέπει να επιλέξουμε μεταξύ της αντιγραφής του κώδικα και της αναδιοργάνωσης του μοντέλου μας - και αυτό μπορεί να είναι μια πραγματικά χρονοβόρα διαδικασία. Μπορούμε να ονομάσουμε τον κώδικα που εκτελεί τη σχέση κληρονομικότητας ως "ανοιχτό-κλειστό" -αυτό σημαίνει ότι είναι ανοιχτός για επεκτάσεις αλλά κλειστός για τροποποιήσεις. Υποθέτοντας ότι στην κλάση Vehicle υπάρχει μια γενική, καθορισμένη λειτουργία κινητήρα, κάθε οχήματος, τη στιγμή που θα θέλαμε να προσθέσουμε ένα μοντέλο οχήματος χωρίς κινητήρα (π.χ. ποδήλατο) στην ιεραρχία των κλάσεών μας, θα έπρεπε να κάνουμε κάποιες σοβαρές αλλαγές στις κλάσεις μας.
κλάση Όχημα
def start_engine
end
def stop_engine
end
end
class Plane < Vehicle
def move
start_engine
...
stop_engine
end
end
Σύνθεση
Αν μας ενδιαφέρει μόνο ένα μέρος της συμπεριφοράς της υπάρχουσας κλάσης, μια καλή εναλλακτική λύση για την κληρονομικότητα είναι η χρήση της σύνθεσης. Αντί να δημιουργούμε υποκλάσεις που κληρονομούν κάθε συμπεριφορά (αυτές που χρειαζόμαστε και αυτές που δεν χρειαζόμαστε καθόλου), μπορούμε να απομονώσουμε τις λειτουργίες που χρειαζόμαστε και να εξοπλίσουμε τα αντικείμενά μας με αναφορές σε αυτές. Με αυτόν τον τρόπο, εγκαταλείπουμε τη σκέψη ότι το αντικείμενο είναι ένα είδος ένα αντικείμενο βάσης, υπέρ του ισχυρισμού ότι περιέχει μόνο ορισμένα τμήματα των ιδιοτήτων του.
Σχ. 2 Χρήση της σύνθεσης
Ακολουθώντας αυτή την προσέγγιση μπορούμε να απομονώσουμε τον κώδικα που είναι υπεύθυνος για τη λειτουργία του κινητήρα στην αυτόνομη κλάση που ονομάζεται Engine και να τοποθετήσουμε αναφορά σε αυτήν μόνο στις κλάσεις που αναπαριστούν οχήματα με κινητήρες. Η απομόνωση των λειτουργιών με τη χρήση της σύνθεσης θα κάνει τη δομή της κλάσης Vehicle απλούστερη και θα ενισχύσει την ενθυλάκωση των επιμέρους κλάσεων. Τώρα, ο μόνος τρόπος με τον οποίο τα οχήματα μπορούν να επηρεάσουν τον κινητήρα είναι να χρησιμοποιήσουν τη δημόσια διεπαφή του, επειδή δεν θα έχουν πλέον πληροφορίες για την υλοποίησή του. Επιπλέον, θα επιτρέπει τη χρήση διαφορετικών τύπων κινητήρων σε διαφορετικά οχήματα, και μάλιστα θα επιτρέπει την ανταλλαγή τους κατά τη διάρκεια της εκτέλεσης του προγράμματος. Φυσικά, η χρήση της σύνθεσης δεν είναι άψογη - δημιουργούμε ένα χαλαρά συνδεδεμένο σύνολο κλάσεων, το οποίο μπορεί εύκολα να επεκταθεί και είναι ανοιχτό για τροποποιήσεις. Αλλά, ταυτόχρονα, κάθε κλάση συνδέεται με πολλές άλλες κλάσεις και πρέπει να έχει πληροφορίες σχετικά με τις διεπαφές τους.
κλάση Όχημα
end
κλάση Engine
def start
end
def stop
end
end
class Αεροπλάνο < Όχημα
def initialize
@engine = Engine.new
end
def move
@engine.start
@engine.stop
end
def change_engine(new_engine)
@engine = new_engine
end
end
Η επιλογή
Και οι δύο περιγραφόμενες προσεγγίσεις έχουν πλεονεκτήματα και μειονεκτήματα, οπότε πώς να επιλέξετε μεταξύ τους; Η κληρονομικότητα είναι μια εξειδίκευση, οπότε είναι καλύτερο να τις εφαρμόζετε μόνο για προβλήματα, στα οποία υπάρχουν σχέσεις τύπου "is-a" - έτσι έχουμε να κάνουμε με την πραγματική ιεραρχία των τύπων. Επειδή η κληρονομικότητα συνδέει στενά τις κλάσεις μεταξύ τους, πρώτα απ' όλα θα πρέπει πάντα να εξετάζουμε αν θα χρησιμοποιήσουμε σύνθεση ή όχι. Η σύνθεση πρέπει να εφαρμόζεται για προβλήματα στα οποία υπάρχουν σχέσεις τύπου "έχει-α" - έτσι η κλάση έχει πολλά μέρη αλλά είναι κάτι περισσότερο από ένα σύνολο κλάσεων. Ένα αεροπλάνο αποτελείται από μέρη αλλά από μόνο του είναι κάτι περισσότερο - έχει πρόσθετες ικανότητες, όπως π.χ. πτήση. Συνεχίζοντας με αυτό το παράδειγμα, τα επιμέρους μέρη μπορούν να εμφανιστούν σε διαφορετικές εξειδικευμένες παραλλαγές, και τότε είναι μια καλή στιγμή για να χρησιμοποιήσετε την κληρονομικότητα.
Η κληρονομικότητα καθώς και η σύνθεση είναι μόνο εργαλεία που έχουν στη διάθεσή τους οι προγραμματιστές, οπότε η επιλογή του κατάλληλου εργαλείου για ένα συγκεκριμένο πρόβλημα απαιτεί εμπειρία. Ας εξασκηθούμε λοιπόν και ας μάθουμε από τα λάθη μας 🙂