Uno dei grandi benefici della programmazione orientata agli oggetti è il riutilizzo del codice, ed uno dei metodi con cui si raggiunge questo obiettivo è attraverso il meccanismo dell'ereditarietà. L'ereditarietà può essere ben immaginata come l'implementazione di una relazione di tipo e sottotipo tra classi.
Supponiamo di voler scrivere un programma che tenga traccia dei professori e degli studenti in una università. Questi possiedono alcune caratteristiche comuni come nome, età ed indirizzo. Possono anche avere delle caratteristiche specifiche come il salario, i corsi e congedi per insegnanti e matricole e le rette per gli studenti.
Si potrebbero creare due classi indipendenti per ogni tipo ed elaborarle all'occorrenza, ma aggiungere una nuova caratteristica comune significherebbe farlo su entrambe le classi indipendentemente: una soluzione che diverrebbe presto difficile da controllare.
Un metodo migliore è senz'altro quello di creare una classe comune chiamata
SchoolMember ed ereditare le classi
insegnanti e studenti dalla stessa, in modo che quelle ereditate siano sotto-tipi
della classe comune, rendendo possibile l'aggiunta di caratteristiche specifiche
senza enormi sacrifici.
Ci sono molti vantaggi usando questo approccio. Le modifiche apportate a qualsiasi
funzionalità in SchoolMember si rifletteranno automaticamente
nei suoi sotto-tipi. Per esempio, si può aggiungere un campo ID sia per gli insegnanti
che per gli studenti semplicemente aggiungendolo alla classe SchoolMember.
Le modifiche ad un sotto-tipo non influenzano altri sotto-tipi. Un vantaggio ulteriore
è che si può far riferimento all' oggetto studente o insegnante come ad un oggetto di
SchoolMember il che potrebbe tornare utile in alcune situazioni
quali contare i membri della scuola. È questo il concetto che viene chiamato
polimorfismo secondo cui un sotto-tipo può essere
sostituito in ogni situazione nella quale ci si aspetta un tipo parente, ovvero tale
oggetto può essere trattato proprio come un'istanza della classe parente.
C'è inoltre da osservare che si riutilizza il codice della classe parente senza la necessità di ripeterlo nelle altre classi come si dovrebbe fare nel caso si usassero classi indipendenti.
La classe SchoolMember in questa situazione viene chiamata
classe base o superclasse.
Le classi Teacher e Student vengono
invece chiamate classi derivate o sottoclassi.
Vediamo adesso l'esempio all'opera.
Esempio 11.5. Usare l'ereditarietà
#!/usr/bin/python # Filename: inherit.py class SchoolMember: '''Represents any school member.''' def __init__(self, name, age): self.name = name self.age = age print '(Initialized SchoolMember: %s)' % self.name def tell(self): '''Tell my details.''' print 'Name:"%s" Age:"%s"' % (self.name, self.age), class Teacher(SchoolMember): '''Represents a teacher.''' def __init__(self, name, age, salary): SchoolMember.__init__(self, name, age) self.salary = salary print '(Initialized Teacher: %s)' % self.name def tell(self): SchoolMember.tell(self) print 'Salary: "%d"' % self.salary class Student(SchoolMember): '''Represents a student.''' def __init__(self, name, age, marks): SchoolMember.__init__(self, name, age) self.marks = marks print '(Initialized Student: %s)' % self.name def tell(self): SchoolMember.tell(self) print 'Marks: "%d"' % self.marks t = Teacher('Mrs. Shrividya', 40, 30000) s = Student('Swaroop', 22, 75) print # stampa una linea vuota members = [t, s] for member in members: member.tell() # funziona sia per Teachers che per Students
$ python inherit.py (Initialized SchoolMember: Mrs. Shrividya) (Initialized Teacher: Mrs. Shrividya) (Initialized SchoolMember: Swaroop) (Initialized Student: Swaroop) Name:"Mrs. Shrividya" Age:"40" Salary: "30000" Name:"Swaroop" Age:"22" Marks: "75"
Per usare l'ereditarietà si specificano i nomi delle classi base in una tupla
che segue il nome della classe nella sua definizione. Il metodo
__init__ della classe base viene chiamato esplicitamente
usando la variabile self in modo che si possa inizializzare
la parte della classe base dell'oggetto. Questo è importante da ricordare;
Python non chiama automaticamente il costruttore della classe base, occorre
farlo esplicitamente.
Un'altra cosa da notare è che si possono chiamare i metodi della classe base
facendoli precedere dal nome della classe per poi passarli alla variabile
self insieme ad altri argomenti.
Da notare che che quando si usa il metodo tell della classe
SchoolMember, si possono trattare istanze di
Teacher o Student come istanze di
SchoolMember.
Inoltre si osservi che viene chiamato il metodo tell
del sotto-tipo e non il metodo tell
della classe SchoolMember. Per comprendere questo punto
ricordiamo che Python parte sempre cercando i metodi nel tipo,
come fa in questo caso. Se non trova il metodo, prosegue la ricerca nelle proprie
classi base, una per una e nell'ordine specificato nella tupla nella definizione
della classe.
Una nota sulla terminologia: se viene listata più di una classe nella tupla che definisce l'ereditarietà, si parla di ereditarietà multipla.