Chissà perché quando si parla di programmazione ad oggetti, quando si insegna, quando si inizia ad applicarla, il concetto che si usa per contraddistinguere il paradigma ad oggetti è l'ereditarietà!
In realtà l'ereditarietà è molto meno utile di quanto si pensa ed è usata spesso in modo errato.
Quando decidiamo di ereditare da una classe per sfruttarne alcune funzionalità dobbiamo prestare attenzione a non violare il principio di sostituzione di Liskov che dice che una classe B può essere sostituita da una sua classe derivata A (cioè A : B) senza modificare il corretto funzionamento del programma.
Questo vuol dire che la classe derivata deve avere "almeno" la stessa interfaccia e le stesse funzionalità della classe base.
Spesso questo principio viene violato.
Supponiamo ad esempio che ci venga richiesta l'implementazione di una classe array che abbia un metodo per ordinare in ordine decrescente i suoi elementi. Il modo più veloce per implementarlo è questo:
public class MyArrayList : ArrayList
{
public override void Sort()
{
// ordino in ordine decrescente gli elementi
}
}
La classe MyArrayList benchè abbia la stessa interfaccia di ArrayList non è sempre sostituibile con un ArrayList perché il suo comportamento è diverso visto che il metodo sort effettua l'ordinamento in ordine inverso.
Quali sono le conseguenze?
Prima una premessa: il principio di Liskov è un concetto teorico, tantè che il codice sopra compila e funziona correttamente.
Dal punto di vista del riutilizzo del codice non ci sono problemi.
Dal punto di vista dell'estendibilità qualche problema può sorgere perché il metodo Sort ha comportamenti diversi a seconda del tipo e mi troverò costretto a scrivere codice che verifica qual è il tipo della classe su cui sto lavorando.
I compilatori ci aiutano solo in parte a rispettare questo principio, infatti in C# una classe non può cambiare i modificatori di accesso dei metodi e proprietà della sua classe base e questo garantisce che le interfacce vengano mantenute. Sul comportamento però c'è poco da fare se non prestare attenzione.
Prima di ereditare da una classe devo chiedermi se effettivamente la classe che sto scrivendo è una classe base in tutto e per tutto, e in tal caso sfrutterò l'ereditarietà per aggiungere funzionalità, non per modificare quelle esistenti.
Per questo qualcuno ha detto "Favor Composition over Inheritance"