Na prośbę exizt rozszerzam swój komentarz na dłuższą odpowiedź.
Największym błędem, jaki popełniają ludzie, jest przekonanie, że interfejsy to po prostu puste klasy abstrakcyjne. Interfejs jest sposobem dla programisty na powiedzenie: „Nie dbam o to, co mi dajesz, o ile przestrzega tej konwencji”.
Biblioteka .NET stanowi wspaniały przykład. Na przykład, kiedy piszesz funkcję, która akceptuje IEnumerable<T>
, mówisz: „Nie obchodzi mnie, jak przechowujesz swoje dane. Chcę tylko wiedzieć, że mogę użyć foreach
pętli.
To prowadzi do bardzo elastycznego kodu. Nagle, aby się zintegrować, wystarczy grać zgodnie z zasadami istniejących interfejsów. Jeśli implementacja interfejsu jest trudna lub myląca, być może jest to wskazówka, że próbujesz wepchnąć kwadratowy kołek w okrągły otwór.
Ale potem pojawia się pytanie: „Co z ponownym użyciem kodu? Moi profesorowie CS powiedzieli mi, że dziedziczenie jest rozwiązaniem wszystkich problemów związanych z ponownym użyciem kodu i że dziedziczenie pozwala pisać raz i używać wszędzie, a leczy zapalenie opon mózgowych w procesie ratowania sierot z podnoszących się mórz i nie byłoby już łez i tak dalej, dalej itd. itd. itd. ”
Używanie dziedziczenia tylko dlatego, że podoba Ci się brzmienie słów „ponowne użycie kodu” jest dość złym pomysłem. Przewodnik po stylowaniu kodu od Google dość zwięźle:
Skład jest często bardziej odpowiedni niż dziedziczenie. ... [B] Ponieważ kod implementujący podklasę jest rozproszony między bazą a podklasą, może być trudniej zrozumieć implementację. Podklasa nie może przesłonić funkcji, które nie są wirtualne, więc podklasa nie może zmienić implementacji.
Aby zilustrować, dlaczego dziedziczenie nie zawsze jest odpowiedzią, zamierzam użyć klasy o nazwie MySpecialFileWriter †. Osoba, która ślepo wierzy, że dziedziczenie jest rozwiązaniem wszystkich problemów, argumentowałaby, że powinieneś spróbować odziedziczyć FileStream
, aby nie powielić FileStream
kodu. Mądrzy ludzie wiedzą, że to głupie. Powinieneś po prostu mieć FileStream
obiekt w swojej klasie (jako zmienną lokalną lub zmienną składową) i korzystać z jego funkcjonalności.
FileStream
Przykładem może wydawać wymyślony, ale tak nie jest. Jeśli masz dwie klasy, które obydwie implementują ten sam interfejs w dokładnie ten sam sposób, powinieneś mieć trzecią klasę, która zawiera dowolną powieloną operację. Twoim celem powinno być pisanie klas, które są samodzielnymi blokami wielokrotnego użytku, które można łączyć jak legos.
Nie oznacza to, że należy unikać dziedziczenia za wszelką cenę. Jest wiele punktów do rozważenia, a większość zostanie omówiona w badaniu pytania „Kompozycja a dziedziczenie”. Nasz własny przepełnienie stosu ma kilka dobrych odpowiedzi na ten temat.
Na koniec dnia uczucia twojego współpracownika nie mają głębi ani zrozumienia niezbędnego do podjęcia świadomej decyzji. Zbadaj temat i sam go wymyśl.
† Przy ilustrowaniu dziedziczenia wszyscy używają zwierząt. To jest bezużyteczne. W ciągu 11 lat rozwoju nigdy nie napisałem klasy o nazwie Cat
, więc nie będę jej używał jako przykładu.
alligator
Implementacjaeat
różni się oczywiście tym, że akceptujecat
idog
jako parametry.