Rozumowanie, czy dziedziczenie (lub jakakolwiek pojedyncza cecha jest naprawdę) jest konieczna, czy nie, bez uwzględnienia reszty semantyki języka jest bezcelowe; kłócisz się w próżni.
Potrzebujesz spójnej filozofii projektowania języka; język musi być w stanie elegancko rozwiązać problemy, dla których jest przeznaczony. Model osiągnięcia tego celu może, ale nie musi, wymagać dziedziczenia, ale trudno jest to ocenić bez dużego obrazu.
Jeśli, na przykład, twój język ma funkcje pierwszej klasy, częściowe zastosowanie funkcji, polimorficzne typy danych, zmienne typów i typy ogólne, masz prawie takie same podstawy jak w przypadku klasycznego dziedziczenia OOP, ale używając innego paradygmatu.
Jeśli masz późne wiązanie, dynamiczne pisanie, metody jako właściwości, argumenty funkcji elastycznych i funkcje pierwszej klasy, obejmujesz również te same podstawy, ale ponownie, używając innego paradygmatu.
(Znalezienie przykładów dla dwóch opisanych paradygmatów pozostawia się jako ćwiczenie dla czytelnika).
Pomyśl więc o semantyce, którą chcesz, baw się z nią i sprawdź, czy są wystarczające bez dziedziczenia. Jeśli nie są, możesz albo zdecydować się na wrzucenie spadku do miksu, albo możesz zdecydować, że czegoś brakuje.
Tak, całkowicie uzasadnioną decyzją projektową jest pominięcie dziedziczenia.
Istnieją w rzeczywistości bardzo dobre powody, aby usunąć dziedziczenie implementacji, ponieważ może ona wytworzyć bardzo skomplikowany i trudny do utrzymania kod. Posunąłbym się nawet do tego, aby uznać dziedziczenie (ponieważ jest ono zwykle realizowane w większości języków OOP) jako błędne.
Na przykład Clojure nie zapewnia dziedziczenia implementacji, woląc udostępnić zestaw funkcji ortogonalnych (protokoły, dane, funkcje, makra), których można użyć do osiągnięcia tych samych wyników, ale o wiele bardziej czysto.
Oto wideo, które uważam za bardzo pouczające na ten ogólny temat, w którym Rich Hickey identyfikuje podstawowe źródła złożoności w językach programowania (w tym dziedziczenie) i przedstawia alternatywne rozwiązania dla każdego z nich: Prostota jest łatwa
źródło
Kiedy po raz pierwszy natknąłem się na fakt, że klasy VB6 nie obsługują dziedziczenia (tylko interfejsy), naprawdę mnie to zirytowało (i nadal działa).
Jednak powodem jest to, że jest tak zły, ponieważ nie miał parametrów konstruktora, więc nie można było wykonać normalnego wstrzykiwania zależności (DI). Jeśli masz DI, to jest to ważniejsze niż dziedziczenie, ponieważ możesz postępować zgodnie z zasadą faworyzowania kompozycji nad dziedziczeniem. To i tak jest lepszy sposób na ponowne użycie kodu.
Nie masz jednak Mixinów? Jeśli chcesz zaimplementować interfejs i przekazać całą pracę tego interfejsu do obiektu ustawionego za pomocą wstrzykiwania zależności, wówczas Mixiny są idealne. W przeciwnym razie musisz napisać cały kod podstawowy, aby przekazać każdą metodę i / lub właściwość do obiektu potomnego. Robię to dużo (dzięki C #) i to jedna rzecz, której żałuję, że nie musiałem robić.
źródło
Aby nie zgodzić się z inną odpowiedzią: nie, wyrzucasz funkcje, gdy są niezgodne z czymś, czego chcesz więcej. Java (i inne języki GC'd) wyrzuciło jawne zarządzanie pamięcią, ponieważ chciało więcej bezpieczeństwa typu. Haskell wyrzucił mutację, ponieważ pragnął więcej równego rozumowania i wymyślnych typów. Nawet C odrzucił (lub uznał za nielegalne) niektóre rodzaje aliasów i inne zachowania, ponieważ chciał więcej optymalizacji kompilatora.
Pytanie brzmi więc: czego chcesz więcej niż dziedziczenia?
źródło
free
/delete
operacja daje możliwość unieważnienia referencji; chyba że Twój system typów jest w stanie śledzić wszystkie odnośniki, których to dotyczy, czyni to język niebezpiecznym. W szczególności ani C, ani C ++ nie są bezpieczne dla typu. Prawdą jest, że możesz dokonać kompromisów w jednym lub drugim (np. Typy liniowe lub ograniczenia alokacji), aby je uzgodnić. Mówiąc ściślej, powinienem był powiedzieć, że Java chce bezpieczeństwa typu ze szczególnym, prostym systemem typów i nieograniczonym przydziałem więcej.null
odwołania). Zakazaniefree
jest dość arbitralnym dodatkowym ograniczeniem. Podsumowując, to, czy język jest bezpieczny dla typu, zależy bardziej od definicji bezpieczeństwa typu niż od języka.T
odwołanie odnosi się donull
rozszerzającego się klasy lub obiektuT
;null
jest brzydka, ale operacje nanull
rzucają dobrze zdefiniowany wyjątek, zamiast niszczenia powyższego niezmiennika typu. Kontrast z C ++: po wywołaniu dodelete
,T*
wskaźnik może wskazywać na pamięć, która nie zawiera jużT
obiektu. Co gorsza, wykonując przypisanie pola za pomocą tego wskaźnika w przypisaniu, możesz całkowicie zaktualizować pole obiektu innej klasy, jeśli zdarzy się, że zostanie umieszczone pod pobliskim adresem. To nie jest typ bezpieczeństwa według jakiejkolwiek użytecznej definicji tego terminu.Nie.
Jeśli chcesz usunąć funkcję języka podstawowego, to ogólnie deklarujesz, że nigdy, nigdy nie jest to konieczne (lub nieuzasadnione trudności we wdrożeniu, co nie ma tu zastosowania). „Nigdy” to mocne słowo w inżynierii oprogramowania. Aby złożyć takie oświadczenie, potrzebujesz bardzo silnego uzasadnienia.
źródło
Mówiąc z perspektywy ściśle C ++, dziedziczenie jest przydatne do dwóch głównych celów:
W przypadku punktu 1, o ile masz sposób na dzielenie się kodem bez konieczności angażowania się w zbyt wiele akrobatyki, możesz zrezygnować z dziedziczenia. Dla punktu 2. Możesz przejść java i nalegać na interfejs do wdrożenia tej funkcji.
Korzyści z usunięcia dziedziczenia są
Kompromis jest w dużej mierze pomiędzy „Dont Powtórz siebie” i elastycznością z jednej strony i unikaniem problemów z drugiej. Osobiście nie chciałbym widzieć dziedziczenia po C ++ po prostu dlatego, że jakiś inny programista może nie być wystarczająco inteligentny, aby przewidzieć problemy.
źródło
Możesz wykonać wiele bardzo przydatnych prac bez dziedziczenia implementacji lub miksów, ale zastanawiam się, czy powinieneś mieć jakieś dziedziczenie interfejsu, tj. Deklarację, która mówi, że jeśli obiekt implementuje interfejs A, to musi również interfejs B (tj. A jest specjalizacją B, więc istnieje związek typu). Z drugiej strony, wynikowy obiekt potrzebuje tylko odnotować, że implementuje oba interfejsy, więc nie ma tam zbyt dużej złożoności. Wszystko doskonale wykonalne.
Brak dziedziczenia implementacji ma jednak jedną wyraźną wadę: nie będziesz w stanie zbudować tabel vtable indeksowanych numerycznie dla swoich klas, a zatem będziesz musiał przeprowadzić wyszukiwanie skrótów dla każdego wywołania metody (lub wymyślić sprytny sposób, aby ich uniknąć). Może to być bolesne, jeśli przekierowujesz nawet podstawowe wartości (np. Liczby) przez ten mechanizm. Nawet bardzo dobra implementacja skrótu może być droga, jeśli trafisz ją wielokrotnie w każdej wewnętrznej pętli!
źródło