Powiedział Jame Gosling
„W miarę możliwości należy unikać dziedziczenia implementacji.”
i zamiast tego użyj dziedziczenia interfejsu.
Ale dlaczego? Jak możemy uniknąć dziedziczenia struktury obiektu za pomocą słowa kluczowego „extends”, a jednocześnie uczynić nasz kod obiektowym zorientowanym?
Czy ktoś mógłby podać obiektowy przykład ilustrujący tę koncepcję w scenariuszu „zamawianie książki w księgarni”?
java
design
object-oriented-design
design-patterns
Nowicjusz
źródło
źródło
Odpowiedzi:
Gosling nie powiedział, że powinien unikać użycia słowa kluczowego extends ... po prostu powiedział, aby nie używać dziedziczenia jako sposobu na ponowne użycie kodu.
Dziedziczenie nie jest złe samo w sobie i jest bardzo potężnym (niezbędnym) narzędziem do tworzenia struktur OO. Jednak gdy nie jest używany właściwie (to jest używany do czegoś innego niż tworzenie struktur obiektowych), tworzy kod, który jest bardzo ściśle powiązany i bardzo trudny w utrzymaniu.
Ponieważ do tworzenia struktur polimorficznych powinno się używać dziedziczenia, można to zrobić równie łatwo za pomocą interfejsów, stąd instrukcja projektowania interfejsów zamiast klas, podczas projektowania struktur obiektów. Jeśli okaże się, że używasz rozszerzeń jako sposobu na uniknięcie wklejania kodu z jednego obiektu do drugiego, być może używasz go nieprawidłowo i lepiej byłoby skorzystać z wyspecjalizowanej klasy do obsługi tej funkcji i zamiast tego udostępnić ten kod poprzez kompozycję.
Przeczytaj o zasadzie substytucji Liskowa i zrozum ją. Ilekroć masz zamiar przedłużyć klasę (lub interfejs w tym zakresie), zadaj sobie pytanie, czy naruszasz zasadę, jeśli tak, to najprawdopodobniej (ab) używasz dziedziczenia w nienaturalny sposób. Jest to łatwe do zrobienia i często wydaje się właściwe, ale utrudni utrzymanie, testowanie i ewolucję kodu.
--- Edytuj Ta odpowiedź stanowi całkiem dobry argument, aby odpowiedzieć na pytanie bardziej ogólnie.
źródło
We wczesnych dniach programowania obiektowego dziedziczenie implementacji było złotym młotem ponownego wykorzystania kodu. Jeśli w jakiejś klasie potrzebna była funkcjonalność, należało ją publicznie odziedziczyć po tej klasie (były to czasy, gdy C ++ był bardziej rozpowszechniony niż Java), a ta funkcjonalność została „za darmo”.
Tyle że nie było tak naprawdę za darmo. W rezultacie powstały niezwykle zawiłe hierarchie dziedziczenia, które były prawie niemożliwe do zrozumienia, w tym drzewa spadkowe w kształcie rombu, które były trudne w obsłudze. Jest to jeden z powodów, dla których projektanci Java postanowili nie obsługiwać wielokrotnego dziedziczenia klas.
Rady Goslinga (i inne stwierdzenia), jakoby były silnym lekarstwem na dość poważną chorobę, i możliwe jest, że niektóre dzieci zostaną wyrzucone z kąpielą. Nie ma nic złego w korzystaniu z dziedziczenia do modelowania relacji między klasami, która naprawdę jest
is-a
. Ale jeśli chcesz po prostu skorzystać z funkcji innej klasy, lepiej najpierw sprawdź inne opcje.źródło
Dziedzictwo zyskało ostatnio dość przerażającą reputację. Jednym z powodów, aby tego uniknąć, jest zasada projektowa „kompozycja nad dziedziczeniem”, która zasadniczo zachęca ludzi do nieużywania dziedziczenia tam, gdzie nie jest to prawdą, tylko po to, aby zaoszczędzić trochę pisania kodu. Ten rodzaj dziedziczenia może powodować trudne do znalezienia i trudne do naprawienia błędy. Powodem, dla którego twój przyjaciel prawdopodobnie sugerował użycie interfejsu, jest to, że interfejsy zapewniają bardziej elastyczne i łatwe w utrzymaniu rozwiązanie dla rozproszonych API. Częściej pozwalają na dokonywanie zmian w interfejsie API bez łamania kodu użytkownika, gdzie ujawnianie klas często łamie kod użytkownika. Najlepszym przykładem tego w .Net jest różnica w użyciu pomiędzy List a IList.
źródło
Ponieważ możesz rozszerzyć tylko jedną klasę, a jednocześnie wdrożyć wiele interfejsów.
Kiedyś słyszałem, że dziedziczenie określa, kim jesteś, ale interfejsy określają rolę, którą możesz odegrać.
Interfejsy pozwalają również na lepsze wykorzystanie polimorfizmu.
To może być część tego powodu.
źródło
Nauczono mnie również tego i wolę interfejsy tam, gdzie to możliwe (oczywiście nadal używam dziedziczenia tam, gdzie ma to sens).
Myślę, że jedną rzeczą jest oddzielenie kodu od konkretnych implementacji. Powiedzmy, że mam klasę o nazwie ConsoleWriter, która zostaje przekazana do metody napisania czegoś i zapisuje to na konsoli. Powiedzmy teraz, że chcę przejść do drukowania do okna GUI. Cóż, teraz muszę zmodyfikować metodę lub napisać nową, która przyjmuje GUIWriter jako parametr. Jeśli zacznę od zdefiniowania interfejsu IWriter i jeśli metoda przyjmie IWriter, mógłbym zacząć od ConsoleWriter (który implementuje interfejs IWriter), a następnie napisać nową klasę o nazwie GUIWriter (która również implementuje interfejs IWriter), a następnie I musiałby po prostu wyłączyć zaliczaną klasę.
Inną rzeczą (co jest prawdą w języku C #, co do Javy nie jest pewne) jest to, że można rozszerzyć tylko 1 klasę, ale zaimplementować wiele interfejsów. Powiedzmy, że miałem zajęcia o nazwie Teacher, MathTeacher i HistoryTeacher. Teraz MathTeacher i HistoryTeacher wychodzą od Nauczyciela, ale co, jeśli chcemy, aby klasa reprezentowała kogoś, kto jest zarówno Nauczycielem matematyki, jak i Nauczycielem historii. Może stać się dość niechlujny, gdy próbujesz odziedziczyć po wielu klasach, gdy możesz to zrobić tylko pojedynczo (istnieją sposoby, ale nie są one dokładnie optymalne). Dzięki interfejsom możesz mieć 2 interfejsy o nazwie IMathTeacher i IHistoryTeacher, a następnie mieć jedną klasę wychodzącą od Nauczyciela i implementującą te 2 interfejsy.
Jedną wadą korzystania z interfejsów jest to, że czasami widzę, jak ludzie duplikują kod (ponieważ musisz utworzyć implementację dla każdej klasy, implementować interfejs), jednak istnieje czyste rozwiązanie tego problemu, na przykład użycie rzeczy takich jak delegaci (nie jestem pewien czym jest odpowiednik Java).
Myślą, że największym powodem używania interfejsów do dziedziczenia jest oddzielenie kodu implementacyjnego, ale nie sądzę, że dziedziczenie jest złe, ponieważ wciąż jest bardzo przydatne.
źródło
Jeśli odziedziczysz po klasie, bierzesz zależność nie tylko od tej klasy, ale musisz także honorować wszelkie ukryte umowy utworzone przez klientów tej klasy. Wujek Bob stanowi świetny przykład tego, jak łatwo subtelnie naruszać Zasadę Zastępstwa Liskowa za pomocą podstawowego kwadratu rozszerzającego dylemat Prostokąt . Interfejsy, ponieważ nie mają implementacji, również nie mają ukrytych umów.
źródło