Dziedziczenie wielokrotne w PHP

97

Szukam dobrego, przejrzystego sposobu obejścia faktu, że PHP5 nadal nie obsługuje wielokrotnego dziedziczenia. Oto hierarchia klas:

Message
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage

Te dwa rodzaje zajęć Invitation * mają ze sobą wiele wspólnego; Chciałbym mieć wspólną klasę rodzicielską, Invitation, po której oboje odziedziczyliby. Niestety mają też wiele wspólnego ze swoimi obecnymi przodkami ... TextMessage i EmailMessage. Klasyczne pragnienie wielokrotnego dziedziczenia tutaj.

Jakie jest najlżejsze podejście do rozwiązania problemu?

Dzięki!

Alex Weinstein
źródło
4
Nie ma wielu przypadków, w których dziedziczenie (lub nawet wielokrotne dziedziczenie) jest uzasadnione. Spójrz na zasady SOLID. Przedkładaj kompozycję nad dziedziczenie.
Ondřej Mirtes
2
@ OndřejMirtes co masz na myśli - „nie ma wielu przypadków, w których dziedziczenie jest uzasadnione.”?
styler1972
12
To znaczy - dziedziczenie przynosi więcej problemów niż korzyści (spójrz na zasadę substytucji Liskova). Możesz rozwiązać prawie wszystko za pomocą kompozycji i zaoszczędzić wiele bólów głowy. Dziedziczenie jest również statyczne - oznacza to, że nie można zmienić tego, co jest już zapisane w kodzie. Ale kompozycja może być używana w czasie wykonywania i możesz dynamicznie wybierać implementacje - np. Ponownie używać tej samej klasy z różnymi mechanizmami buforowania.
Ondřej Mirtes
5
PHP 5.4 ma „cechy”: stackoverflow.com/a/13966131/492130
f.ardelian
1
Sugerowałbym początkującym, aby nigdy nie używali dziedziczenia . Ogólnie rzecz biorąc, jedyne dwie sytuacje, w których dziedziczenie jest dozwolone, to: 1) podczas budowania biblioteki, więc użytkownicy piszą mniej kodu i 2) gdy kierownik projektu wymaga, abyś go
użył

Odpowiedzi:

141

Alex, w większości przypadków dziedziczenie wielokrotne jest sygnałem, że struktura obiektu jest nieco niepoprawna. W sytuacji, którą opisałeś, widzę, że odpowiedzialność klasowa jest po prostu zbyt szeroka. Jeśli Message jest częścią modelu biznesowego aplikacji, nie powinien zajmować się renderowaniem danych wyjściowych. Zamiast tego możesz podzielić odpowiedzialność i użyć MessageDispatcher, który wysyła wiadomość przekazaną za pomocą zaplecza tekstowego lub html. Nie znam twojego kodu, ale pozwól mi to zasymulować w ten sposób:

$m = new Message();
$m->type = 'text/html';
$m->from = 'John Doe <[email protected]>';
$m->to = 'Random Hacker <[email protected]>';
$m->subject = 'Invitation email';
$m->importBody('invitation.html');

$d = new MessageDispatcher();
$d->dispatch($m);

W ten sposób możesz dodać specjalizację do klasy Message:

$htmlIM = new InvitationHTMLMessage(); // html type, subject and body configuration in constructor
$textIM = new InvitationTextMessage(); // text type, subject and body configuration in constructor

$d = new MessageDispatcher();
$d->dispatch($htmlIM);
$d->dispatch($textIM);

Zauważ, że MessageDispatcher podejmie decyzję, czy wysłać jako HTML, czy zwykły tekst, w zależności od typewłaściwości przekazanej w obiekcie Message.

// in MessageDispatcher class
public function dispatch(Message $m) {
    if ($m->type == 'text/plain') {
        $this->sendAsText($m);
    } elseif ($m->type == 'text/html') {
        $this->sendAsHTML($m);
    } else {
        throw new Exception("MIME type {$m->type} not supported");
    }
}

Podsumowując, odpowiedzialność podzielona jest na dwie klasy. Konfiguracja wiadomości odbywa się w klasie InvitationHTMLMessage / InvitationTextMessage, a algorytm wysyłania jest delegowany do dyspozytora. Nazywa się to wzorcem strategii, więcej na ten temat możesz przeczytać tutaj .

Michał Rudnicki
źródło
13
Niezwykle obszerna odpowiedź, dziękuję! Nauczyłem się czegoś dzisiaj!
Alex Weinstein,
26
... Wiem, że to jest trochę stare (szukałem, czy PHP ma MI ... tylko z ciekawości) Nie sądzę, że jest to dobry przykład wzorca strategii. Wzorzec strategii jest zaprojektowany tak, aby w dowolnym momencie można było wdrożyć nową „strategię”. Dostarczona przez Ciebie implementacja nie ma takiej możliwości. Zamiast tego Message powinien mieć funkcję "send", która wywołuje MessageDispatcher-> dispatch () (Dispatcher albo parametr, albo zmienna składowa), a nowe klasy HTMLDispatcher i TextDispatcher zaimplementują "wysyłkę" na swój odpowiedni sposób (to pozwala innym dyspozytorom zrobić inna praca)
Terence Honles
12
Niestety PHP nie nadaje się do implementacji wzorca strategii. Języki obsługujące przeciążanie metod działają tutaj lepiej - wyobraź sobie, że masz dwie metody o tej samej nazwie: wysyłka (HTMLMessage $ m) i wysyłka (TextMessage $) - teraz w silnie typizowanym języku kompilator / interpreter automatycznie zastosuje właściwą „strategię” opartą na rodzaj parametru. Poza tym nie sądzę, że bycie otwartym na wdrażanie nowej strategii jest istotą Strategy Pattern. Jasne, że to miło, ale często nie jest to wymóg.
Michał Rudnicki
2
Załóżmy, że masz klasę Tracing(to tylko przykład), w której chcesz mieć ogólne rzeczy, takie jak debugowanie w pliku, wysyłanie wiadomości SMS w przypadku krytycznego problemu i tak dalej. Wszystkie twoje klasy są dziećmi tej klasy. Teraz przypuśćmy, że chcesz utworzyć klasę, Exceptionktóra powinna mieć te funkcje (= dziecko Tracing). Ta klasa musi być dzieckiem Exception. Jak projektujesz takie rzeczy bez wielokrotnego dziedziczenia? Tak, zawsze możesz mieć rozwiązanie, ale zawsze będziesz blisko włamania. A hakowanie = drogie rozwiązanie na dłuższą metę. Koniec opowieści.
Olivier Pons
1
Olivier Pons, nie sądzę, aby śledzenie podklasy było właściwym rozwiązaniem dla twojego przypadku użycia. Coś tak prostego, jak posiadanie abstrakcyjnej klasy Tracing ze statycznymi metodami Debug, SendSMS itp., Które można następnie wywołać z dowolnej innej klasy za pomocą Tracing :: SendSMS () itd. Twoje inne klasy nie są „typami” Tracing, „używają” śledzenia. Uwaga: niektórzy wolą singleton zamiast statycznych metod; W miarę możliwości wolę używać metod statycznych zamiast pojedynczych.
15

Może możesz zastąpić relację „jest-jest” relacją „ma-a”? Zaproszenie może zawierać wiadomość, ale niekoniecznie musi to być wiadomość „jest”. Może zostać potwierdzone np. Zaproszenie, co nie pasuje do modelu Message.

Wyszukaj „skład a dziedziczenie”, jeśli chcesz dowiedzieć się więcej na ten temat.

Matthias Kestenholz
źródło
9

Jeśli mogę zacytować Phila w tym wątku ...

PHP, podobnie jak Java, nie obsługuje dziedziczenia wielokrotnego.

W PHP 5.4 pojawią się cechy, które będą próbować rozwiązać ten problem.

W międzyczasie najlepiej przemyśleć projekt swojej klasy. Możesz zaimplementować wiele interfejsów, jeśli szukasz rozszerzonego interfejsu API do swoich klas.

I Chris ....

PHP tak naprawdę nie obsługuje dziedziczenia wielokrotnego, ale istnieje kilka (nieco niechlujnych) sposobów jego implementacji. Sprawdź ten adres URL, aby zobaczyć kilka przykładów:

http://www.jasny.net/articles/how-i-php-multiple-inheritance/

Myślałem, że obaj mają przydatne linki. Nie mogę się doczekać, aby wypróbować cechy, a może niektóre mieszanki ...

Simon East
źródło
1
Cechy są do zrobienia
Jonathan
6

Framework Symfony ma do tego wtyczkę mixin , możesz chcieć to sprawdzić - nawet dla pomysłów, jeśli nie chcesz go używać.

Odpowiedzią na „wzorzec projektowy” jest wyodrębnienie współdzielonej funkcjonalności w osobnym komponencie i komponowanie w czasie wykonywania. Pomyśl o sposobie wyodrębnienia funkcji Invitation jako klasy, która zostanie powiązana z Twoimi klasami Message w inny sposób niż dziedziczenie.

joelhardi
źródło
4

Używam cech w PHP 5.4 jako sposobu rozwiązania tego problemu. http://php.net/manual/en/language.oop5.traits.php

Pozwala to na klasyczne dziedziczenie z rozszerzeniami, ale także daje możliwość umieszczenia wspólnych funkcjonalności i właściwości w „cesze”. Jak mówi instrukcja:

Cechy to mechanizm ponownego wykorzystania kodu w pojedynczych językach dziedziczenia, takich jak PHP. Cecha ma na celu zmniejszenie niektórych ograniczeń pojedynczego dziedziczenia poprzez umożliwienie programiście swobodnego ponownego używania zestawów metod w kilku niezależnych klasach żyjących w różnych hierarchiach klas.

MatthewPearson
źródło
3

Wygląda na to, że wzór dekoratora może być odpowiedni, ale trudno to stwierdzić bez dodatkowych szczegółów.

danio
źródło
Najlepsza odpowiedź IMO.
l00k
3

To jest zarówno pytanie, jak i rozwiązanie ...

A co z magicznym _ wywołaniem (),metody _get (), __set ()? Nie testowałem jeszcze tego rozwiązania, ale co jeśli utworzysz klasę multiInherit. Chroniona zmienna w klasie potomnej może zawierać tablicę klas do dziedziczenia. Konstruktor w klasie z wieloma interfejsami mógłby tworzyć wystąpienia każdej dziedziczonej klasy i łączyć je z własnością prywatną, np. _Ext. Metoda __call () może użyć funkcji method_exists () na każdej z klas w tablicy _ext, aby zlokalizować poprawną metodę do wywołania. __get () i __set mogą zostać użyte do zlokalizowania właściwości wewnętrznych lub jeśli jesteś ekspertem z referencjami, możesz sprawić, że właściwości klasy potomnej i klas dziedziczonych będą odwołaniami do tych samych danych. Wielokrotne dziedziczenie obiektu byłoby niewidoczne dla kodu używającego tych obiektów. Również, obiekty wewnętrzne mogą w razie potrzeby uzyskiwać bezpośredni dostęp do dziedziczonych obiektów, o ile tablica _ext jest indeksowana według nazwy klasy. Wyobraziłem sobie stworzenie tej superklasy i jeszcze jej nie zaimplementowałem, ponieważ uważam, że jeśli to zadziała, może to doprowadzić do rozwoju różnych złych nawyków programistycznych.

Ralph Ritoch
źródło
Myślę, że jest to wykonalne. Będzie łączyć funkcjonalność wielu klas, ale w rzeczywistości ich nie odziedziczy (w sensie instanceof)
user102008
I to z pewnością nie pozwoli na nadpisanie, gdy tylko w klasie wewnętrznej wykona wezwanie do siebie :: <cokolwiek>
Phil Lello
1

Mam kilka pytań, które pozwolą Ci wyjaśnić, co robisz:

1) Czy obiekt wiadomość prostu zawierać np treść wiadomości, odbiorcę, TERMINARZ? 2) Co zamierzasz zrobić z obiektem zaproszenia? Czy trzeba go traktować specjalnie w porównaniu z wiadomością e-mail? 3) Jeśli tak, CO jest w tym takiego specjalnego? 4) Jeśli tak, to dlaczego typy wiadomości wymagają odmiennej obsługi w przypadku zaproszenia? 5) A co jeśli chcesz wysłać wiadomość powitalną lub wiadomość OK? Czy to też nowe obiekty?

Wygląda na to, że próbujesz połączyć zbyt wiele funkcji w zestaw obiektów, które powinny zajmować się tylko przechowywaniem treści wiadomości - a nie tym, jak należy z nią postępować. Jak dla mnie widzisz, nie ma różnicy między zaproszeniem a zwykłą wiadomością. Jeśli zaproszenie wymaga specjalnej obsługi, oznacza to logikę aplikacji, a nie typ wiadomości.

Na przykład: system, który zbudowałem, miał współdzielony podstawowy obiekt wiadomości, który został rozszerzony na SMS, e-mail i inne typy wiadomości. Jednak: nie były one dalej przedłużane - wiadomość z zaproszeniem była po prostu wstępnie zdefiniowanym tekstem, który miał być wysłany za pośrednictwem wiadomości typu E-mail. Konkretny wniosek o zaproszenie dotyczyłby walidacji i innych wymagań dotyczących zaproszenia. W końcu wszystko, co chcesz zrobić, to wysłać wiadomość X do odbiorcy Y, który powinien być systemem dyskretnym sam w sobie.


źródło
0

Ten sam problem co Java. Spróbuj użyć interfejsów z funkcjami abstrakcyjnymi do rozwiązania tego problemu

DeeCee
źródło
0

PHP obsługuje interfejsy. To może być dobry zakład, w zależności od Twoich zastosowań.

Cheekysoft
źródło
5
Interfejsy nie pozwalają na konkretne implementacje funkcji, więc nie są tutaj pomocne.
Alex Weinstein,
1
Interfejsy obsługują wielokrotne dziedziczenie, w przeciwieństwie do klas.
Craig Lewis
-1

A co z klasą Invitation znajdującą się bezpośrednio pod klasą Message?

więc hierarchia idzie:

Wiadomość
--- Zaproszenie
------ Wiadomość tekstowa
------ Wiadomość e-mail

W klasie Invitation dodaj funkcjonalność, która była w InvitationTextMessage i InvitationEmailMessage.

Wiem, że zaproszenie nie jest tak naprawdę typem wiadomości, to bardziej funkcjonalność wiadomości. Więc nie jestem pewien, czy to dobry projekt OO, czy nie.

nube
źródło