Uno dei precetti di base della programmazione orientata agli oggetti è che la sottotipizzazione rappresenta la relazione "è-a". Cioè, il bambino è sempre una forma specifica del genitore. Un esempio comune è che un quadrato è una forma specifica di rettangolo, quindi sembra logico che una classe Square
venga ereditata da una classe Rectangle
. (leggi qui perché questo in realtà non ha senso - grazie a @KevinKrumwiede per averlo indicato)
Occasionalmente, si incontra una situazione in cui la sottotipizzazione è un modo conveniente per scrivere una relazione diversa.
Quello che attualmente affronto è relativamente semplice. Sto controllando un semplice sistema meccanico B, che contiene il sottosistema A. Quindi, nello spazio della carne, vediamo una relazione "ha-a" molto letterale: il sistema B ha un sistema A.
Scrivendo il codice come una stretta analogia con la realtà, mi sono ritrovato con qualcosa di simile al seguente: (Mostrato in Python per brevità. Il codice reale è in C ++.)
class A:
def m1(self): print "A.m1"
def m2(self): print "A.m2"
def m3(self, arg): print "A.m3(" + arg + ")"
class B:
def __init__(self): self.a = A()
def m1(self): a.m1()
def m2(self): a.m2()
def m3(self): print "B.m3"
def m4(self): a.m3("B.m4")
Sebbene la classe B
contribuisca a funzionalità utili (nella versione di vita reale), molti dei suoi metodi sono semplici pass-through. La classe B
non ha nulla di utile da aggiungere a m1()
o m2()
- deve solo esporli ai proprietari di oggetti B
.
Se eliminiamo il precetto che l'ereditarietà dovrebbe rappresentare una relazione "è-a", potremmo semplificare le cose:
class A:
def m1(self): print "A.m1"
def m2(self): print "A.m2"
def m3(self, arg): print "A.m3(" + arg + ")"
class B(A):
def __init__(self): pass
def m3(self): print "B.m3"
def m4(self): a.m3("B.m4")
Ora tutti i metodi di A
sono esposti come se appartenessero a B
. B
può (e fa) sovrascrivere i metodi di A
come necessario. Il maintainer di B
non ha bisogno di aggiornare B
ogni volta che A
aggiunge un nuovo metodo - i passthrough sono gratuiti.
In qualche modo questo sembra sbagliato, ma non riesco a capire del tutto perché. Si tratta di un abuso dei tecnicismi del meccanismo ereditario? È mai appropriato rappresentare una relazione "ha-a" in questo modo? B
è non una forma più specifica di A
, ma l'abbiamo sottoclassata come se fosse.
L'unico inconveniente pratico che riesco a pensare è che i metodi tutti di A
sono ora disponibili per la chiamata e B
non ha alcun modo per impedirlo. In un linguaggio come Python, non esiste un concetto di metodi privati, quindi non importa. Potrebbe in una lingua come C ++. Esistono altri svantaggi?
Modifica: per tutti coloro che troveranno questa domanda in futuro, assicurati di leggere questa risposta così come la risposta accettata . Entrambi elencano gli svantaggi degni di nota nell'usare l'ereditarietà per realizzare la composizione.