Ereditarietà e polimorfismo
Introduzione
Sulla programmazione orientata agli oggetti e i suoi concetti fondamentali (ereditarietà, polimorfismo, incapsulamento), esiste abbondante documentazione in rete, per cui non mi ci soffermerò qui.
Ereditarietà
L'ereditarietà nella programmazione orientata agli oggetti consiste nella possibilità di definire delle classi sulla base di altre classi già esistenti.
Vediamo un semplice esempio in Python. Immaginiamo di voler definire delle classi per la rappresentazione di semplici figure geometriche piane, di cui ci interessano il nome, l'area, il perimetro, ecc.
Possiamo procedere definendo una classe base e poi le classi derivate, come nel seguente esempio:
class Shape(object):
'''Un oggetto di tipo Shape rappresenta una figura geometrica generica'''
@property
def area(self):
'''Restituisce l'area della figura'''
pass
@property
def perimeter(self):
'''Restituisce il perimetro della figura'''
pass
@property
def name(self):
return getattr(self, '_name', 'Untitled')
# restituisce l'attributo _name se esiste, altrimenti 'Untitled'
La classe Shape deriva dalla classe object, la classe antenata di tutte le classi.
Se poi volessimo definire le classi Rectangle e Circle potremmo farle derivare dalla classe Shape:
class Rectangle(Shape):
def __init__(self, width, height, name="A rectangle"):
self._width=width
self._height=height
self._name=name
@property
def perimeter(self):
return 2*(self._height+self._width)
@property
def area(self):
return self._height * self._width
class Circle(Shape):
def __init__(self, radius):
self._radius=radius
@property
def perimeter(self):
return self._radius*math.pi*2
@property
def area(self):
return math.pi * self._radius ** 2
Nel programma principale potremo quindi creare delle istanze di Rectangle e Circle, e richiamare i metodi delle classi specifiche oppure quelli della classe base:
c=Circle(4)
print('Area del cerchio:', c.area)
print('Perimetro del cerchio:', c.perimeter)
print('Nome:', c.name)
print(type(c))
Area del cerchio: 50.2654824574
Perimetro del cerchio: 25.1327412287
Nome: Untitled
<class '__main__.Circle'>
r=Rectangle(10,20)
print('Area del rettangolo:', r.area)
print('Perimetro del rettangolo:', r.perimeter)
print('Nome:', r.name)
print(type(r))
Area del rettangolo: 200
Perimetro del rettangolo: 60
Nome: A rectangle
<class '__main__.Rectangle'>
Polimorfismo
Il polimorfismo fa sì che gli oggetti si comportino in maniera diversa a seconda della classe a cui appartengono, quando viene invocato uno specifico metodo (o quando viene richiesto l'accesso ad una proprietà).
Consideriamo il seguente esempio, in cui abbiamo una lista di figure geometriche, per tutti gli elementi della quale vogliamo visualizzare nome e area:
shapes=[
Circle(5),
Rectangle(9,10),
Rectangle(11,21,'R1'),
]
for s in shapes:
print(s.name, s.area)
L'output sarà, come ci aspettiamo, il seguente:
Untitled 78.5398163397
A rectangle 90
R1 231
Si noti che viene sempre richiamato il metodo o la proprietà della classe di appartenenza dell'oggetto, mai quello della classe base. In altri linguaggi, come il C++, questo comportamento, chiamato late-binding, è presente solo nel caso di definizione esplicita dei metodi come virtuali nella classe base.
Overriding
Quando si ridefinisce un metodo in una classe derivata, si parla di overriding. A volte può essere utile e/o necessario richiamare comunque, nell'implementazione del metodo nella classe derivata, le azioni della classe base. Si può procedere come nell'esempio seguente, in cui è stato aggiunto alla classe base il metodo place() per indicare le coordinate dell'estremo in alto a sinistra della figura geometrica. Nelle classi Rectangle e Circle si sfrutta il metodo della classe base e poi si imposta il valore del punto centrale della figura.
class Shape(object):
...
def place(self, x, y):
'''Imposta le coordinate dell'estremo NW della figura'''
self._x=x
self._y=y
@property
def center(self):
'''Restituisce la tupla di coordinate del centro della figura'''
return self._center
class Rectangle(Shape):
...
def place(self, x, y):
super().place(x, y)
self._center=(self._x+self._width/2, self._y+self._height/2)
class Circle(Shape):
...
def place(self, x, y):
super().place(x, y)
self._center=(self._x+self._radius, self._y+self._radius)
Nel programma principale creeremo un'istanza e poi potremo impostarne le coordinate, visualizzando poi la tupla delle coordinate del centro:
c=Circle(4)
c.place(-1,12)
print('Centro del cerchio:', c.center)
Centro del cerchio: (3, 16)
r=Rectangle(10,20)
r.place(4,-2)
print('Centro del rettangolo:', r.center)
Centro del rettangolo: (9.0, 8.0)