chiaro scuro wrote:
Interessante il disorso sull’ “essere” e sul “trattare come se”.
Più uso ruby, più tendo a delegittimare il valore dell’ essere/is_a in
favore del “trattare come”/respond_to. Anche l’ereditarietà sembra in
questo contesto perdere un pò di senso, e sto osservando il mio stile
che di conseguenza tende a cambiare.
Voi cosa ne pensate? Qual’è il ruolo dell’ereditarietà in un linguaggio
come ruby?
Dicevo… .per me è una questione di DRY al 90% l’ereditarietà (oltre che
nel caso di modellare la realtà esistente).
Mah… qua è partito un flash di riflessioni un po’ confuse ma corpose:
In tempi di Duck Typing ridiventa certamente di moda la distinzione
fra classi e tipi che si trova per esempio nel capitolo introduttivo di
Design Patterns di Gamma, Helm, Johnson e Vlissides.
La maggior parte delle persone sono oggi abituate a linguaggi come
Java o C++ dove classi e tipi in buona sostanza coincidono (con la
possibile eccezione della programmazione generica).
Un tipo è il nome usato per denotare una particolare interfaccia.
Questo non ha direttamente a che vedere con le Interfaces del Java, per
inciso. L’interfaccia è l’insieme di metodi (o di messaggi, per dirla
con Ruby, Smalltalk o ObjectiveC) cui un oggetto risponde.
Tuttavia un oggetto può implementare diversi tipi, e tipi diversi
possono essere condivisi da oggetti molto diversi fra loro. In questo i
tipi si comportano come le interfacce Java. Qualunque programmatore Java
sa che ci sono Interfacce (uso il maiuscolo per indicare che intendo la
parola nel senso “Javesco”) supportate da diversi oggetti (leggi
Cloneable, per esempio) e che un qualunque oggetto può implementare un
numero grande a piacere di Interfacce.
La differenza è che le Interfacce di Java sono statiche e “legate”
alle classi. Una data classe dice di implementare un certo numero di
interfacce. Le istanze di quella classe avranno quindi per tipo le varie
interfacce.
In Ruby tuttavia questo non accade. Un oggetto ha un dato tipo perché
risponde a determinati messaggi, ma non specifichiamo da nessuna parte
quale sia il tipo. Per il solo fatto di rispondere a certi messaggi un
oggetto è di un dato tipo (a prescindere dalla sua classe). I tipi sono
“informali” (usando la stessa differenza fra protocolli formali e
informali di ObjectiveC – i protocolli formali si comportano
sostanzialmente in modo simile alle Interfacce di Java, quelli informali
sono appunti “non staticamente tipizzati”).
La classe è invece legata direttamente all’implementazione. Citando DP
del GOF
"È importante capire la differenza fra la classe di un oggetto e il
suo tipo. La classe definisce come è stato implementato. Definisce il
suo stato interno e l’implementazione delle sue operazioni. D’altra
parte il tipo di un oggetto si riferisce solo alla sua interfaccia,
all’insieme di richieste a cui può rispondere. Un oggetto può avere
molti tipi e oggetti di classi differenti possono avere lo stesso tipo
Naturalmente c’è una una stretta parentela fra classe e tipo. Poiché
una classe definisce le operazioni che un oggetto è in grado di
eseguire, definisce anche il suo tipo. Quando diciamo che un oggetto è
un’istanza di una classe, diciamo implicitamente che l’oggetto supporta
l’interfaccia definita dalla classe.
Linguaggi come C++ e Eifell (e Java ndt) usano le classi per
specificare
sia il tipo di un oggetto che la sua implementazione"
D’altra parte linguaggi dinamici come Ruby o Python (o Smalltalk) non
dichiarano i tipi delle variabili. Mandano messaggi (o chiamano metodi,
per dirla con Python) agli oggetti denotati dalle variabili e se tali
oggetti supportano il messaggio, tutto
funzionerà.
La differenza fra ereditarietà di classe e ereditarietà di tipo è
importante. L’ereditarietà di classe riguarda definire l’implementazione
di un oggetto attraverso l’implementazione di un altro oggetto. È senza
dubbio un concetto “DRY”. Se ho già definito delle cose (e gli oggetti
sono sufficientemente vicini) posso mantenerle uguali. In Ruby a questo
si associano i mixin che permettono di condividere codice senza andare
in ereditarietà (ma questa è un’altra storia), in Python si può usare
l’ereditarietà multipla per emulare i mixin.
L’ereditarietà di tipo è invece relativa all’utilizzare un oggetto al
posto di un altro. In questo senso siamo più vicini al Principio di
Sostituzione di Barbara Liskov. Un ottimo articolo relativo al LSP si
trova qui (Vacation Rental Hacks). In
buona sostanza possiamo enunciare il Principio di Sostituzione di Liskov
come:
T1 è un sottotipo di T2 se per ogni oggetto O1 di tipo T1 esiste un
oggetto O2 di tipo T2 tale che per ogni programma definito in termini di
T1 il comportamento del programma è invariato sostituendo O2 al posto di
O1.
Confondere ereditarietà di tipo e di classe è facile. Molti linguaggi
non hanno alcuna distinzione fra le due. Anzi, vengono usate le classi
per definire i tipi. Molti programmatori per esempio vedono il LSP solo
in relazione alle sottoclassi (in effetti in C++ è importante fare in
modo tale che gli oggetti si comportino “bene” in relazione al LSP, in
quanto è sempre possibile fare puntare un puntatore o una reference di
un supertipo ad un oggetto del suo sottotipo).
In realtà il principio di Liskov è una cosa molto severa. Due
oggetti sono sostituibili secondo Liskov se davvero (limitatamente al
tipo comune) si comportano allo stesso modo, e viene naturale pensarli
come classe - sottoclasse (anche se effettivamente questo non è
necessario).
Il DuckTyping è meno severo: non chiede che il programma abbia lo
stesso comportamento. Due oggetti sono appunto sostituibili se
rispondono alle stesse cose, anche se rispondono in modo diverso. Non ha
nulla a che fare con il Design By Contract: è molto più vicino a
quello che in C++ si fa con i templates (che alcuni in effetti vedono
come il corrispettivo statico del Duck Typing).
Tornando a noi, se un oggetto si comporta secondo un certo tipo
(ovvero risponde ai metodi propri di quel tipo), allora trattiamolo come
tale. Da cui: se si comporta come una papera, trattiamolo come una
papera (Duck Typing, appunto).
Un linguaggio come Ruby rende molto problematico considerare come
stretta la relazione fra tipi e classi. In ogni punto del programma
(anche se non è sempre buona pratica farlo) possiamo cambiare il tipo di
tutti gli oggetti di una data classe (aggiungendo o togliendo metodi
alla stessa), o singolarmente ad un singolo oggetto. Sintomatico è in
questo senso avere deprecato il metodo Object#type in favore di
Object#class.
–
blog: http://www.akropolix.net/rik0/blogs | Enrico F.
site: http://www.akropolix.net/rik0/ | RiK0
forum: http://www.akropolix.net/forum/ | Usenet: [email protected]