Διάλεξη 28ης Απριλίου 2015 - Κλάσεις και αντικείμενα. Η κληρονομικότητα
Στις προηγούμενες διαλέξεις είδαμε πως να ορίσουμε μια κλάση και πως να κατασκευάσουμε αντικείμενα μιας συγκεκριμένης κλάσης, ή όπως λέμε στη γλώσσα του αντικειμενοστραφούς προφραμματισμού να κατασκευάσουμε στιγμιότυπα (instances) μιας κλάσης. Σήμερα πραγματευόμαστε ένα ακόμα χαρακτηριστικό των κλάσεων που ονομάζεται κληρονομικότητα (inheritance). Εξηγούμε τη συγκεκριμένη έννοια με ένα παράδειγμα. Ορίζουμε την κλάση Pet (κατοικίδιο ζώο) με χαρακτηριστικά το όνομά του (name), και το είδος του (species). Έτσι, μπορούμε να ορίσουμε την κλάση Pet ως
class Pet:
def __init__(self, name, species):
self.name = name;
self.species = species
def getName(self):
return self.name
def getSpecies(self):
return self.species
def __str__(self):
return '%s is a %s' % (self.name, self.species)
και να την χρησιμοποιήσουμε όπως στο παράδειγμα που ακολουθεί:
>>> tim = Pet("Tim", "dog")
>>> felix = Pet("Felix", "cat")
>>> polly = Pet("Polly", "parrot")
>>> print tim
Tim is a dog
>>> print felix
Felix is a cat
>>> print polly
Polly is a parrot
>>> felix.getSpecies()
'cat'
Φυσικά, η κλάση Pet περιγράφει οποιοδήποτε κατοικίδιο ζώο και ειδικώτερα ένα σκύλο ή μια γάτα. Στους σκύλους, για παράδειγμα, αρέσει, συνήθως, να κυνηγούν γάτες. Αν θέλαμε να ξέρουμε σε ποιούς σκύλους αρέσει να κυνηγούν γάτες θα έπρεπε στην κλαση Pet να προσθέσουμε ένα ακόμα χαρακτηριστικό, ας το ονομάσουμε chases_cats, με τιμή True αν αρέσκεται να κυνηγάει γάτες, False διαφορετικά. Καταλαβαίνουμε αμέσως ένα φανερό μειονέκτημα αυτής της μεθόδου. Αν υποθέσουμε ότι μόνο οι σκύκλοι αρέσκονται να κυνηγούν γάτες, τότε το χαρακτηριστικό chases_catς δεν έχει νόημα για οποιοδήποτε είδος κατοικίδιου ζώου εκτός από τον σκύλο. Μια καλύτερη λύση θα ήταν νο ορίσουμε την κλάση Dog, η οποία να "κληρονομήσει" από την κλάση Pet τα χαρατηριστικά name και species αλλά να ορίσει και το χαρακτηριστικό που χρειαζόμαστε τώρα, δηλαδή το chases_cats. Αυτό μπορεί να γίνει με τον ακόλουθο τρόπο:
class Dog(Pet):
def __init__(self, name, chases_cats):
Pet.__init__(self, name, "dog")
self.chases_cats = chaces_cats
def chasesCats(self):
return self.chases_cats
Αξίζει να σχολιάσουμε ορισμένα σημεία του παραπάνω κώδικα. Κατ' αρχήν ο ορισμός της κλάσης Dog με την
δήλωση class Dog(Pet)
σημαίνει ότι η κλάσει Dog __init__
και αυτό είναι
φυσιολογικό μια και πρέπει, πέρα από τα χαρακτηριστικά name και species να ορίσουμε, όπως είπαμε, το
χαρακτηριστικό chases_cats. Παρατηρούμε ότι για να κατασκευάσουμε ένα αντικείμενο τύπου Dog κατασκευάζουμε
ένα αντικείμενο τύπου Pet με τον κατασκευαστή της κλάσης Pet και επιπλέον ορίζουμε το επιπλέον
χαρακτηριστικό chases_cats. Το αντικείμενο λοιπόν που φτιάξαμε έχει όνομα (name), τό είδος του είναι "dog"
και έχει το χαρακτηριστικό chases_cats. Φροντίσαμε επίσης να ορίζουμε τη μέθοδο chasesCats
η οποία επιστρέφει το χαρακτηριστικό chases_cats. Όπως είπαμε και πριν, η κλάση Dog κληρονομεί τις μεθόδους
getName()
και getSpecies()
από την κλάση Pet.
Φυσικά θα μπορούσαμε να ορίσουμε και την κλάση Cat, ως κλάση που παράγεται από την κλάση Pet με το επιπλέον χαρακτηριστικό hates_dog (μισεί τους σκύλους):
class Cat(Pet):
def __init__(self, name, hates_dogs):
Pet.__init__(self, name, "cat")
self.hates_dogs = hates_dogs
def hatesDogs(self):
return self.hates_dogs
Ασ δούμε τώρα πως μπορούμε να χρησιμοποιήσουμε τις τρεις αυτές κλάσεις που ορίσαμε και να καταλάβουμε τις διαφορές μεταξύ αντικειμένων τύπου Pet και Dog ή Cat. Ξεκινάμε με τα αντικείμενα
>>> mypet = Pet("Spot", "dog")
>>> mydog = Dog("Spot", True)
Από τη συζήτηση παραπάνω είναι προφανές ότι το αντικείμεο mypet είναι τύπου Pet και οι τιμές των χαρακτηριστικών του name και species είναι αντίστοιχα, "Spot" και "dog". Το αντικείμενο mydog όμως είναι τύπου Dog και οι τιμές των χαρακτηριστικών του name, species και chases_cats είναι αντίστοιχα, "Spot", "dog" και True.
Με τη χρήση της συνάρτησης isinstance
(την έχουμε ξαναδεί!) τον τύπο ενός
αντικειμένου. Για τα αντικείμενα mypet και mydog έχουμε:
>>> isinstance(mypet, Pet)
True
>>> isinstance(mypet, Dog)
False
>>> isinstance(mydog, Pet)
True
>>> isinstance(mydog, Dog)
True
Επιβεβαιώνουμε λοιπόν ότι το αντικείμενο mypet είναι τύπου Pet αλλά όχι τύπου Dog, ενώ το αντικείμενο mydog είναι τύπου Dog και, φυσικά, τύπου Pet. Ας δούμε επίσης ποιές μέθοδοι ορίζονται για τις δύο αυτές κλάσεις:
>>> mypet.chasesCats()
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'Pet' object has no attribute 'chasesCats'
>>> mydog.chasesCats()
True
>>> mypet.getName()
'Spot'
>>> mydog.getName()
'Spot'
Δείτε ότι η κλάση Dog κληρονήμησε την μέθοδο getName από την κλάση Pet ενώ αντιθέτως η μέθοδος chasesCats() δεν ορίζεται για αντικείμενα τύπου Pet. Ακολουθούν μερικά ακόμα παραδείγματα:
>>> frisky = Dog("Frisky", True)
>>> rover = Dog("Rover", False)
>>> mittens = Cat("Mittens", True)
>>> flufffy = Cat("Fluffy", False)
>>> print frisky
Frisky is a dog
>>> print rover
Rover is a dog
>>> print mittens
Mittens is a cat
>>> print fluffy
Fluffy is a cat
>>> print '%s chases cats: %s' % (frisky.getName(), frisky.chasesCats())
Frisky chases cats: True
>>> print '%s hates dogs: %s' % (fluffy.getName(), fluffy.hatesDogs())
Fluffy hates dogs: False
Ακολουθεί ένα ακόμα παράδειγμα με τις κλάσεις Person και Employee:
class Person:
def __init__(self, first, last):
self.firstname = first
self.lastname = last
def getName(self):
return self.firstname + " " + self.lastname
class Employee(Person):
def __init__(self, first, last, num):
Person.__init__(first, last)
self.staffNumber = num
deg getNumber(self)
return self.getName() + "," + self.staffNumber
>>> x = Person("Marge", "Simpson")
>>> y = Employee("Home", "Simpson", "1099")
>>> print x.getName()
Marge Simpson
>>> print y.getNumber()
Homer Sƣmpson, 1099
Μπορούμε να δώσουμε και άλλα παραδείγματα κλάσεων όπου η έννοια της κληρονικότηρας βρίσκει εφαρμογή. Ως πιθανά παραδείγματα αναφέρουμε την κλάση Rectangle και την παραγόμενη από αυτή κλάση Square, ή την κλάση Vehicle (όχημα) και τις παραγόμενες από αυτήν κλάσεις Car, Bus, Truck και Bicycle. Ο ενδιαφερόμενος αναγνώστης καλείται να σκεφτεί τα παραδείγματα αυτά ως προς τα χαρακτηριστικά των κλάσεων και των μεθόδων τους.