Articoli di programmazione

Di ereditarietà, composizione e polimorfismo

L'ereditarietà? Bella ma non ci vivrei. Meglio una bella composizione con la sua interfaccia in mezzo a tanto polimorfismo!

cropped-programmazione-relazionale-730x415-c-default.jpg
Per prima cosa diciamo cosa è il #Polimorfismo per noi informatici. La parola ci rimanda al concetto di avere molte forme pur restando se stessi, per cui di fatto nel senso ristretto correlato alla programmazione a oggetti, polimorfismo si collega al meccanismo con cui una certa entità (classe, metodo o altro) esegue diverso codice in base a diverse condizioni, pur rimanendo se stessa. Ancora troppo generico vero?
Facciamo così, ora elenco i vari tipi di polimorfismo ed alla fine sono sicuro che avrai capito il concetto:
Polimorfismo ad hoc
Si ha quando chiami un metodo Add con 2 parametri Int e ti restituisce un risultato Int che è la somma dei due.
Ma allo stesso tempo chiami un altro metodo Add a cui passi due stringhe e ti restituisce la concatenazione di esse.
Add è polimorfico perché pur essendo sempre se stesso, assume diverse forme, cioè somma o concatena in base a quali parametri riceve.
Polimorfismo parametrico
Quando usi Generics in C# e Java, fai uso di questo tipo di polimorfismo. Scrivi un metodo generico, che accetta quindi un parametro di Tipo, per cui in base al Tipo con cui operi, il metodo letteralmente si adegua e lavora con esso.
Ad esempio scrivi una funzione Add<T> e questa eseguirà codice diverso in base a T che gli passi.
Polimorfismo per inclusione
Quando scrivi una super-classe e delle sotto-classi che eseguono sovrascrittura dei metodi base, fai uso di questo polimorfismo. Allo stesso modo diventi polimorfico quando fai composizione e scrivi un'interfaccia poi la implementi in modo diverso in più classi.
Questo è il tipo di polimorfismo su cui si basano moltissimi design patterns che appunto consentono di dividere il codice tra più classi polimorfiche (tramite ereditarietà o composizione). In questi pattern c'è sempre una classe che in qualche modo esegue codice diverso, in base all'entità polimorfica che solitamente gli viene iniettata dall'esterno.

Abbiamo parlato dell'ereditarietà. L'ereditarietà è una specie di moda, appena l'hai notata e l'hai apprezzata inizi a vederla ovunque e da lì alla fine il passo è breve: inizierai ad usarla, sempre di più, finché non sarà troppo tardi!
Perché sono così critico con l'ereditarietà? Perché è potente, forse troppo e rende le cose complicate, anche qui forse troppo. E la complessità è il nemico di ogni sistema manutenibile.

Praticamente tutto quello che puoi ottenere in termini di flessibilità con l'ereditarietà (relazione "è un"), puoi ottenerlo con la composizione (relazione "ha un") e qualche interfaccia ben studiata: fai uso comunque di #polimorfismo che tanto è importante nelle nostre architetture modulari e flessibili senza dover pagare il prezzo dell'ereditarietà. Sì perché l'ereditarietà costa in termini di manutenibilità, prestazioni e complessità.

Vediamo qualche differenza tra #ereditarietà e #composizione.
Prima di tutto se erediti da una classe, lo scrivi in modo statico nel codice: è scritto nella pietra e non puoi cambiarlo per nessun motivo. Invece se usi una interfaccia, puoi cambiare la classe che la implementa e che usi tramite composizione, quando vuoi, anche a runtime. Già questo mi sembra un ottimo motivo per preferire la composizione.

Andiamo avanti.

Da quante classi puoi ereditare? Beh dipende dal linguaggio, ma tendenzialmente la maggior parte dei linguaggi non implementa l'ereditarietà multipla, quindi per chi usa c# e Java la risposta tende ad essere "UNA SOLA".
Quante altre classi invece puoi aggregare? Onestamente non so se c'è un limite ma certamente è un numero estremamente alto. Certamente non è detto che tu debba farlo, ma avere la possibilità di utilizzare tramite composizione più di una classe, è certamente qualcosa di utile.

Proseguiamo.

Il #TDD! Nessuno pensa al TDD?!
Con cosa fai i #Mock e gli #Stub? Con le super-classi o con le interfacce?
Lapidario e lampante: se fai TDD devi lavorare con le interfacce e la composizione. Non c'è alternativa.
E questo aspetto è quello che per me rappresenta il motivo più importante per relegare l'ereditarietà in un angolo buio della cantina ed andarla a spolverare solo in rarissimi casi.

Non basta? Andiamo avanti.

Incapsulamento. A me piacciono le classi bene incapsulate. Non mi piace quando altro codice ficca il naso all'interno delle mie classi; come biasimarmi?
Non mi piace il fatto che una super-classe possa in ogni momento modificare qualche porzione del proprio codice ed andare a stravolgere la logica delle mie sotto-classi. La mia classe è mia solamente e la gestisco io: in tutto e per tutto e non deve cambiare se io non voglio; tantomeno se varia la sua super-classe. Qualcuno direbbe "padroni a casa nostra"! (:^D

Per ora basta così, anche se la lista non finisce qui, perché è tardi ed è meglio andare a dormire: domani devo essere molto polimorfico in ufficio.

#programmazione #codice #architettura