Un noto difetto delle gerarchie di classi tradizionali è che sono cattive quando si tratta di modellare il mondo reale. Ad esempio, cercando di rappresentare le specie animali con classi. In realtà ci sono diversi problemi nel farlo, ma uno a cui non ho mai visto una soluzione è quando una sottoclasse "perde" un comportamento o una proprietà definita in una superclasse, come un pinguino che non è in grado di volare (lì sono probabilmente esempi migliori, ma questo è il primo che mi viene in mente).
Da un lato, non si desidera definire, per ogni proprietà e comportamento, qualche flag che specifica se è presente, e controllarlo ogni volta prima di accedere a quel comportamento o proprietà. Vorresti solo dire che gli uccelli possono volare, in modo semplice e chiaro, nella classe Bird. Ma sarebbe bello se in seguito si potessero definire "eccezioni", senza dover usare alcuni orribili hack ovunque. Ciò accade spesso quando un sistema è stato produttivo per un po '. All'improvviso trovi una "eccezione" che non si adatta affatto al design originale e non vuoi modificare gran parte del codice per adattarlo.
Quindi, ci sono alcuni linguaggi o schemi di progettazione in grado di gestire in modo chiaro questo problema, senza richiedere importanti modifiche alla "superclasse" e tutto il codice che lo utilizza? Anche se una soluzione gestisce solo un caso specifico, diverse soluzioni potrebbero insieme formare una strategia completa.
Dopo aver pensato di più, mi rendo conto di aver dimenticato il principio di sostituzione di Liskov. Ecco perché non puoi farlo. Supponendo che tu definisca "tratti / interfacce" per tutti i principali "gruppi di caratteristiche", puoi implementare liberamente tratti in diversi rami della gerarchia, come il tratto Volante potrebbe essere implementato da Uccelli e alcuni tipi speciali di scoiattoli e pesci.
Quindi la mia domanda potrebbe equivalere a "Come potrei non implementare un tratto?" Se la tua superclasse è un serializzabile Java, devi esserlo anche tu, anche se non c'è modo di serializzare il tuo stato, ad esempio se contenevi un "socket".
Un modo per farlo è quello di definire sempre tutti i tratti in coppia dall'inizio: Flying e NotFlying (che genererebbe UnsupportedOperationException, se non confrontato). Il Non-tratto non definirebbe alcuna nuova interfaccia e potrebbe essere semplicemente verificato. Sembra una soluzione "economica", in particolare se utilizzata dall'inizio.
" it would be nice if one could define "exceptions" afterward, without having to use some horrible hacks everywhere"
consideri un metodo di fabbrica che controlla il comportamento hacky?
NotSupportedException
da Penguin.fly()
.
class Penguin < Bird; undef fly; end;
. Se dovresti essere un'altra domanda.
function save_yourself_from_crashing_airplane(Bird b) { f.fly() }
sarebbe molto più complicato. (come ha detto Peter Török, viola LSP)