Γλώσσα Προγραμματισμού ΙΙ

Διάλεξη 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 παράγεται από την κλάση Dog και ουσιαστικά κληρονομεί τα χαρακηριστικά και τις μεθόδους της κλάσης Pet. Παρατηρούμε ακόμη ότι στην κλάση Pet ορίζεται εκ νέου ο κατασκευαστής __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. Ο ενδιαφερόμενος αναγνώστης καλείται να σκεφτεί τα παραδείγματα αυτά ως προς τα χαρακτηριστικά των κλάσεων και των μεθόδων τους.