Pominięcie dziedziczenia w językach programowania


10

Rozwijam własny język programowania. Jest to język ogólnego przeznaczenia (pomyśl statycznie napisany Python na pulpicie, tj. int x = 1;) , Nieprzeznaczony dla chmury.

Czy uważasz, że nie można pozwolić na dziedziczenie lub miksy? (biorąc pod uwagę, że użytkownik miałby przynajmniej interfejsy)

Na przykład: Google Go, język systemowy, który zszokował społeczność programistów, nie zezwalając na dziedziczenie.

Odpowiedzi:


4

W rzeczywistości odkrywamy, że stosowanie dziedziczenia jest nieoptymalne, ponieważ (między innymi) prowadzi do ścisłego powiązania.

Dziedziczenie implementacji można zawsze zastąpić dziedziczeniem interfejsu plus kompozycja, a bardziej nowoczesne projekty oprogramowania zmierzają w kierunku coraz mniejszego dziedziczenia na korzyść kompozycji.

Więc tak , nie zapewniając w szczególności dziedziczenia jest całkowicie poprawny i bardzo modne decyzja projektowa.

Z drugiej strony, miksy nie są nawet (jeszcze) głównym językiem, a języki, które zapewniają funkcję o nazwie „miksy” często rozumieją przez to bardzo różne rzeczy. Możesz je podać, czy nie. Osobiście uważam, że jest to bardzo przydatne, ale jego prawidłowe wdrożenie może być bardzo trudne.


13

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.


9

Tak.

Myślę, że niedopuszczenie dziedziczenia jest w porządku, szczególnie jeśli Twój język jest dynamicznie wpisywany. Podobny kod można ponownie wykorzystać, na przykład przez delegację lub kompozycję. Na przykład napisałem kilka średnio skomplikowanych programów - dobra, nie tak skomplikowanych:) - w JavaScript bez żadnego dziedziczenia. Zasadniczo użyłem obiektów jako algebraicznych struktur danych z niektórymi funkcjami dołączonymi jako metody. Miałem też kilka funkcji, które nie były metodami działającymi na tych obiektach.

Jeśli masz dynamiczne pisanie - i zakładam, że masz - możesz mieć polimorfizm również bez dziedziczenia. Ponadto, jeśli zezwolisz na dodawanie dowolnych metod do obiektów w czasie wykonywania, tak naprawdę nie będziesz potrzebować takich rzeczy jak mixiny.

Inną opcją - która moim zdaniem jest dobra - jest emulacja JavaScript i używanie prototypów. Są to prostsze niż klasy, ale także bardzo elastyczne. Tylko coś do rozważenia.

Tak więc, wszystko powiedziane, wybrałbym to.


2
Tak długo, jak istnieje rozsądny sposób na wykonanie zadań zwykle wykonywanych z dziedziczeniem, kontynuuj. Możesz wypróbować kilka przypadków użycia, aby się upewnić (być może nawet posunąć się do rozwiązania innych problemów, aby uniknąć uprzedzeń ...)
Stormstorm

Nie Jest statycznie i mocno wpisany, wspomnę o tym na górze.
Christopher

Ponieważ jest on wpisany statycznie, nie można po prostu dodawać losowych metod do obiektów w czasie wykonywania. Nadal jednak możesz pisać na klawiaturze: sprawdź na przykład protokoły Gosu . Podczas lata użyłem trochę protokołów i były one bardzo przydatne. Gosu ma również „ulepszenia”, które są sposobem na dodanie metod do klas po fakcie. Możesz również rozważyć dodanie czegoś takiego.
Tikhon Jelvis

2
Proszę, proszę, proszę bardzo, przestańcie wiązać dziedziczenie i ponowne użycie kodu. Od dawna wiadomo, że dziedziczenie jest naprawdę złym narzędziem do ponownego użycia kodu.
Jan Hudec

3
Dziedziczenie jest tylko jednym narzędziem do ponownego wykorzystania kodu. Często jest to bardzo dobre narzędzie, a innym razem okropne. Preferowanie kompozycji zamiast dziedziczenia nie oznacza, że ​​w ogóle nigdy nie stosuje się dziedziczenia.
Michael K

8

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


W wielu przypadkach dziedziczenie interfejsu jest bardziej odpowiednie. Ale jeśli jest używane z umiarem, dziedziczenie implantacji może pozwolić na usunięcie dużej ilości kodu płyty wzorcowej i jest najbardziej eleganckim rozwiązaniem. Wymaga tylko programistów, którzy są bardziej inteligentni i ostrożni niż przeciętna małpa w języku Python / JavaScript.
Erik Alapää,

4

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ć.


Tak, co skłoniło to pytanie do wdrożenia SVG DOM, w którym ponowne użycie kodu jest bardzo korzystne.
Christopher

2

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?


1
Tyle tylko, że jawne zarządzanie pamięcią i bezpieczeństwo typów są całkowicie niezwiązane i zdecydowanie nie są niezgodne. To samo dotyczy mutacji i „fantazyjnych typów”. Zgadzam się z ogólnym rozumowaniem, ale twoje przykłady są raczej źle wybrane.
Konrad Rudolph

@KonradRudolph, zarządzanie pamięcią i bezpieczeństwo typów są ściśle powiązane. A free/ deleteoperacja 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.
Ryan Culpepper

To raczej zależy od tego, jak zdefiniujesz bezpieczeństwo typu. Na przykład, jeśli weźmiesz (całkowicie uzasadnioną) definicję bezpieczeństwa typu kompilacji i chcesz, aby integralność referencji była częścią twojego systemu typów, wówczas Java również nie jest bezpieczna dla typu (ponieważ zezwala na nullodwołania). Zakazanie freejest 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.
Konrad Rudolph

1
@Ryan: Wskaźniki są całkowicie bezpieczne dla typu. Zawsze zachowują się zgodnie ze swoją definicją. Może nie tak, jak lubisz, ale zawsze zależy to od ich definicji. Próbujesz je rozciągnąć, aby były czymś, czym nie są. Inteligentne wskaźniki mogą zagwarantować bezpieczeństwo pamięci dość trywialnie w C ++.
DeadMG

@KonradRudolph: W Javie każde Todwołanie odnosi się do nullrozszerzającego się klasy lub obiektu T; nulljest brzydka, ale operacje na nullrzucają dobrze zdefiniowany wyjątek, zamiast niszczenia powyższego niezmiennika typu. Kontrast z C ++: po wywołaniu do delete, T*wskaźnik może wskazywać na pamięć, która nie zawiera już Tobiektu. 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.
Ryan Culpepper

1

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.


8
Nie jest to prawdą: cały projekt Javy polegał na usuwaniu funkcji takich jak przeciążanie operatora, wielokrotne dziedziczenie i ręczne zarządzanie pamięcią. Ponadto uważam, że traktowanie jakichkolwiek cech języka jako „świętych” jest błędne; zamiast uzasadniać usunięcie funkcji, powinieneś uzasadnić jej dodanie - nie mogę wymyślić żadnych funkcji, które każdy język musi mieć.
Tikhon Jelvis

6
@TikhonJelvis: Właśnie dlatego Java jest okropnym językiem i brakuje w nim czegokolwiek poza „WYKORZYSTANIEM DZIEDZICTWA ZBIORCZEGO ” jest powodem numer jeden . Zgadzam się, że funkcje językowe powinny być uzasadnione, ale ta jest podstawową funkcją języka - ma wiele przydatnych aplikacji i nie może być powielona przez programistę bez naruszenia DRY.
DeadMG

1
@DeadMG wow! Całkiem mocny język.
Christopher

@DeadMG: Zacząłem pisać obalenie jako komentarz, ale potem przekształciłem je w odpowiedź (qv).
Ryan Culpepper

1
„Doskonałość jest osiągana nie wtedy, gdy nie ma już nic do dodania, ale kiedy nie ma już nic do usunięcia” (q)
9000

1

Mówiąc z perspektywy ściśle C ++, dziedziczenie jest przydatne do dwóch głównych celów:

  1. Umożliwia wielokrotnego użytku kodu
  2. W połączeniu z przesłonięciem w klasie potomnej pozwala na użycie wskaźnika klasy bazowej do obsługi obiektu klasy potomnej bez znajomości jego typu.

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ą

  1. zapobiegać długim hierarchiom i związanym z tym problemom. Twój mechanizm współdzielenia kodu powinien być na to wystarczająco dobry.
  2. Unikaj zmian w klasach rodzicielskich przed rozbijaniem klas potomnych.

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.


1

Czy uważasz, że nie można pozwolić na dziedziczenie lub miksy? (biorąc pod uwagę, że użytkownik miałby przynajmniej interfejsy)

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!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.