W jaki sposób mixiny lub cechy są lepsze od zwykłego wielokrotnego dziedziczenia?

54

C ++ ma zwykłe wielokrotne dziedziczenie, wiele projektów językowych zabrania go jako niebezpiecznego. Ale niektóre języki, takie jak Ruby i PHP, używają dziwnej składni, aby robić to samo i nazywać to mixins lub cechami. Słyszałem wiele razy, że miksy / cechy są trudniejsze do nadużywania niż zwykłe wielokrotne dziedziczenie.

Co konkretnie czyni je mniej niebezpiecznymi? Czy jest coś, co nie jest możliwe w przypadku mixin / cech, ale jest możliwe w przypadku wielokrotnego dziedziczenia w stylu C ++? Czy można z nimi spotkać problem z diamentem?

Wydaje się, że używamy wielokrotnego dziedziczenia, ale po prostu usprawiedliwiamy, że są to miksy / cechy, abyśmy mogli ich użyć.

Gherman
źródło
7
Czekam na kogoś, kto opublikuje dobrze napisaną, przemyślaną i dokładną odpowiedź na to pytanie.
Robert Harvey
7
Ruby i PHP nie wprowadziły miksów i cech. Mixiny zostały wprowadzone w Flavours (1980), cechy zostały wprowadzone w Squeak Smalltalk (2001). Smaki były pierwszym językiem obiektowym o wielokrotnym dziedziczeniu i używały miksów. C ++ uzyskał wielokrotne dziedzictwo tylko w wersji 2.0, która została wydana w 1989 roku, 9 lat po Flavours. Pytanie powinno zatem brzmieć: Smaki mają proste miksy, ale niektóre języki, takie jak C ++, wprowadzają dziwną składnię, aby robić to samo i nazywać to wielokrotnym dziedziczeniem.
Jörg W Mittag

Odpowiedzi:

31

Istnieje wiele problemów z wielokrotnym dziedziczeniem, gdy jest ono używane z pełnoprawnymi klasami, ale wszystkie dotyczą niejednoznaczności .

Dwuznaczność ujawnia się na kilka różnych sposobów:

  1. Jeśli masz dwie klasy podstawowe z tym samym polem x, a typ pochodny pyta o xto, co on otrzymuje?
    • Jeśli dwie xzmienne mają niespójne typy, możesz je wywnioskować.
    • Jeśli są tego samego typu, możesz spróbować połączyć je w tę samą zmienną.
    • Zawsze możesz je ujawnić jako dziwne, w pełni kwalifikowane nazwy.
  2. Jeśli masz dwie klasy podstawowe z tą samą funkcją fi identycznymi podpisami, a ktoś dzwoni f, który zostaje wywołany?
    • Co jeśli dwie klasy podstawowe mają wspólnego wspólnego wirtualnego przodka (problem z diamentem).
    • Co jeśli funkcja ma inne, ale kompatybilne podpisy?
  3. Kiedy konstruujesz klasę z dwiema klasami podstawowymi, który z konstruktorów klas podstawowych jest nazywany pierwszy? Kiedy niszczysz obiekt, który zostaje zabity?
  4. Jak konsekwentnie układasz obiekt w pamięci?
  5. Jak poradzisz sobie z tymi wszystkimi przypadkami z 3 klasami podstawowymi? 10?

I to ignoruje takie rzeczy jak dynamiczne wysyłanie, wnioskowanie o typie, dopasowywanie wzorców i inne rzeczy, o których mniej wiem, które stają się trudniejsze, gdy język obsługuje wielokrotne dziedziczenie pełnych klas.

Cechy lub wtyczki (lub interfejsy lub ...) to konstrukcje, które konkretnie ograniczają możliwości danego typu, dzięki czemu nie ma dwuznaczności. Rzadko są właścicielami czegokolwiek. Pozwala to na płynniejszą kompozycję typów, ponieważ nie ma dwóch zmiennych lub dwóch funkcji ... istnieje zmienna i odwołanie; funkcja i podpis. Kompilator wie, co robić.

Innym powszechnie stosowanym podejściem jest zmuszanie użytkownika do „budowania” (lub mieszania) swojego typu pojedynczo. Zamiast klas podstawowych będących równorzędnymi partnerami w nowym typie, dodajesz jeden typ do drugiego - zastępując wszystko, co tam było (zwykle z opcjonalną składnią, aby zmienić nazwę i / lub ponownie ujawnić przesłonięte bity).

Czy jest coś, co nie jest możliwe w przypadku mixin / cech, ale jest możliwe w przypadku wielokrotnego dziedziczenia w stylu C ++?

W zależności od języka - na ogół staje się kłopotliwe lub niemożliwe do scalenia implementacji funkcji i pamięci dla zmiennych z wielu klas bazowych i ujawnienia ich w typie pochodnym.

Czy można z nimi spotkać problem z diamentem?

Czasami pojawiają się mniej poważne odmiany w zależności od języka, ale zwykle nie. Cały sens cech polega na przełamaniu tego rodzaju dwuznaczności.

Telastyn
źródło
Czy możesz podać przykład języka / technologii pozwalający na tworzenie takich typów „instancji” dla instancji?
Gherman
@ niemiecki - z pewnością nie jestem ekspertem Scala, ale rozumiem, że właśnie to withrobi słowo kluczowe.
Telastyn
„konstrukcje, które konkretnie ograniczają możliwości danego typu, aby nie było dwuznaczności”: jakie ograniczenia? Interfejsy nie mają zmiennych, więc jest to jeden limit, ale cechy Scala i moduły Ruby. Czy są ograniczone w inny sposób?
David Moles,
@DavidMoles - jest to powszechny sposób, widziałem również ograniczenia dotyczące wirtualnych reguł wysyłania (pomyśl jawnie zaimplementowane interfejsy C #).
Telastyn