Questo è correlato al problema dei diamanti . O la lingua che offre ereditarietà multiple ha il supporto esplicito per evitare i problemi correlati, oppure la soluzione migliore è evitare l'ereditarietà (multipla) .
Fammi ricapitolare il problema in pseudocodice:
class ClassA { X() { ... } }
class ClassB extends ClassA { X() { ClassA.X(); ... } }
class ClassC extends ClassA { X() { ClassA.X(); ... } }
class ClassD extends ClassB, ClassC {
X() { ClassB.X(); ClassC.X(); ... }
}
In questo progetto in cui ClassB e ClassC chiamano anche il loro metodo super in ClassA, una chiamata a ClassD.X()
finirà per chiamare ClassA.X()
due volte . Questo è l'approccio alla risoluzione prima della profondità dell'ereditarietà multipla utilizzata in C ++ e potrebbe o meno essere quello che desideri.
Altre lingue cercano di ordinare ("linearizzare") le classi base in modo che possiamo semplicemente chiamare la prossima classe base, senza sapere in anticipo quale classe sarà. Python è uno di questi linguaggi:
class ClassA(object):
def x(self):
...
class ClassB(ClassA):
def x(self):
super().x(); ...
class ClassC(ClassA):
def x(self):
super().x(); ...
class ClassD(ClassB, ClassC):
def x(self):
super().x(); ...
Per ClassD, l'algoritmo di ordine di risoluzione del metodo C3 (MRO) ordinerà le classi come ClassD, ClassB, ClassC, ClassA
e ricerca le classi per i metodi da sinistra a destra. La chiamata super().x()
non chiama nella classe base, ma continua la ricerca nella MRO per quell'oggetto. In questo caso, la super-chiamata dal metodo ClassB.x()
verrà effettivamente risolta in ClassC.x()
. Esistono limitazioni a questo approccio:
- Ciò richiede il supporto esplicito dal modello a oggetti del linguaggio. Non è una funzionalità che può essere adattata in un secondo momento.
- L'MRO risultante può essere poco intuitivo, perché non viene necessariamente inviato alla classe base, ma può essere inviato a classi apparentemente non correlate.
- Tutte le classi in una gerarchia di ereditarietà multipla devono collaborare. Tutti devono inoltrare correttamente le chiamate
super().x()
, altrimenti si interromperanno. Se diverse classi richiedono argomenti diversi, passare gli argomenti può essere complicato (questo è spesso un problema con il metodo __init__()
di Python che funge da costruttore).
A causa di questi problemi, pochissime lingue supportano l'ereditarietà multipla (in particolare Python, C ++ e Perl). Anche in queste lingue, l'eredità multipla è generalmente scoraggiata. Altre lingue usano tratti o mixin per fornire alcuni aspetti dell'ereditarietà multipla, senza dover definire un ordine di risoluzione dei metodi complicato.
In uno scenario in cui desideri aggiungere dinamicamente funzionalità ad alcuni oggetti, il modello di decorazione potrebbe essere più appropriato. Richiede solo l'ereditarietà dell'interfaccia e evita completamente i problemi dell'ereditarietà delle classi.
- Definisci un'interfaccia come
interface CanDoX { X(); }
- Fornisci un'implementazione di base, ad es. %codice%
-
Implementa vari decoratori che implementano class ClassA implements CanDoX { ... }
e prendi un'istanza CanDoX
nel loro costruttore:
class DecorateXWithB implements CanDoX {
private CanDoX base;
X() { base.X(); ... }
}
-
Ora puoi assemblare una pipeline di decoratori esattamente nell'ordine desiderato:
new DecorateXWithD(
new DecorateXWithC(
new DecorateXWithB(
new ClassA(...))))