Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Kompozycja vs dziedziczenie
Forum PHP.pl > Forum > PHP
lukaskolista
Cześć, mam problem z decyzją, czy zastosować kompozycję czy dziedziczenie.

Stan aktualny:
MatchInterface (interface)
Match (class, implementuje MatchInterface)

Chciałbym stworzyć nowy rodzaj Matcha (dopasowania), który zyskuje nową metodę "getName()" oraz argument konstruktora względem klasy Match. W tym celu dodałem nowy interface oraz klasę:
NamedMatchInterface (interface dziedziczący po MatchInterface dodający nową metodą getNamed())
NamedMatch (class, implementuje NamedMatchInterface)

Proszę nie zwracać uwagi na na nazewnictwo, jest to PSR ale nie wykluczone, że zmienię tę konwencję, bo mi trochę nie odpowiada.

Moje pytanie brzmi: klasa "NamedMatch" powinna dziedziczyć po klasie "Match" i dodawać nową metodę oraz nadpisać konstruktor, czy jednak zastosować kompozycję?
Z punktu widzenia testów jednostkowych oraz możliwości rozwoju skłaniam się do kompozycji, jednak chciałbym poznać Wasze zdanie.

Edit:
Niby to powinna być kompozycja, bo dochodzi nowa metoda, jednak 4 inne metody są identyczne jak w klasie Match i w przypadku zastosowania kompozycji będą po prostu proxowane do klasy Match.
rafkon1990
Kompozycję stosuje się wtedy, gdy między klasami zachodzi relacja typu „całość ↔ część” tzn. nowa klasa zawiera w sobie istniejącą klasę.
Dziedziczenie stosuje się wtedy, gdy między klasami zachodzi relacja „generalizacja ↔ specjalizacja” tzn. nowa klasa jest szczególnym rodzajem już istniejącej klasy.

Myślę, że w twoim przypadku jest dziedziczenie.
lukaskolista
Dokładnie na odwrót - kompozycja. Nowa metoda nie jest specjalizacją klasy, a dodatkową funkcjonalnością.
Poza tym trafiłeś na jedną z licznych zasad prezentowanych na blogach.

Klasy Match i NamedMatch są na tym samym poziomie specjalizacji, przy czym NamedMatch ma jedną funkcjonalność więcej, reszta jest wspólna.
Pyton_000
NamedMatchInterface powinien rozszerzać MatchInterface, a klasy implementować odpowiedni interface.
lukaskolista
Cytat
NamedMatchInterface powinien rozszerzać MatchInterface, a klasy implementować odpowiedni interface.

Bez urazy, ale co to ma do tematu? Napisałeś to, co ja napisałem już na samym początku tego wątku.

MatchInterface -> Match
MatchInterface -> NamedMatchInterface -> NamedMatch

Chodzi o to, czy zastosować kompozycję, czy dziedziczenie.
Pyton_000
Ok spoko. Ja bym zastosował dziedziczenie. Nie warto chyba w tym przypadku komplikować sobie życia kompozycją.
rafkon1990
Cytat(lukaskolista @ 4.12.2016, 15:14:45 ) *
Dokładnie na odwrót - kompozycja. Nowa metoda nie jest specjalizacją klasy, a dodatkową funkcjonalnością.
Poza tym trafiłeś na jedną z licznych zasad prezentowanych na blogach.

Klasy Match i NamedMatch są na tym samym poziomie specjalizacji, przy czym NamedMatch ma jedną funkcjonalność więcej, reszta jest wspólna.


Edit. Zerknąłem do googla i masz rację - jest to kompozycja.
lukaskolista
Teraz jeszcze zostaje kwestia interfaceow. Czy NamedMatchInterface moze dziedziczyc po MatchInterface, czy jednak powinien specyfikować kontrakt na nowo? Co do tego to nie mam pojecia bo kompozycje rozpatruje się w kontekście implementacji a nie abstrakcji, przynajmniej o abstrakcji nic nie mogę znaleźć.
Pyton_000
Raczej powinien dziedziczyć, tym bardziej że kontrakt jest taki sam wspólny.
lukaskolista
Chyba tak jak piszesz interface powinien dziedziczyć, bo opisuje kontrakt, a nie implementację, natomiast implementacja powinna być zrealizowana za pomocą kompozycji.

Tak z ciekawości zapytam:
Jak testujecie klasy, które po sobie dziedziczą? Przykładowo mamy klasę A z metodą x() i y() + mamy testy do tej klasy i metody. Do tego mamy klasę B też z metodą x(), która jest odziedziczona z klasy A, natomiast metoda y() jest inna. Jak testujecie klasę B? Piszecie testy dla metody x() w obu klasach? Dodatkowo co w przypadku, gdy klasa potomna korzysta z metody rodzica + dodaje swoją logikę - nie da się zamockować klasy rodzica, bo dziecziczenie to mechaizm języka.
Pyton_000
Normalnie. Testujesz klasę której używasz. Więc jeśli używasz B to testujesz metody których używasz. Ciebie nie interesuje że w B jest tylko zmieniona Y a X jest brana z A, Ciebie interesuje wynik metody. Bo jeśli zmieni się metoda A::X to Twoj kod przestaje działać.
Pilsener
Cytat
czy zastosować kompozycję czy dziedziczenie.
- pytanie dziwne, bo to dwa przeciwstawne wzorce.

I "część wspólna" to nie jest powód, by dziedziczyć czy tym bardziej tworzyć kompozycję. To wg mnie podstawowy błąd. Tak się robiło w XIX wieku, gdzie programy były statyczne i pisane z myślą o tym, że nigdy nie będą zmieniane, W dobie adżajlów używanie wzorców, które bazują na bardzo sztywnej relacji pomiędzy obiektami (jak dziedziczenie) jest sztuką dla sztuki. Dziedziczenie pięknie wygląda tylko w teorii, w praktyce zaraz zmieni się w stertę antywzorców takich jak jojo czy callsuper.

Trzeba zawsze dążyć do tego, aby implementacja była jak najprostsza i jak najbardziej skalowalna.
Jeśli mamy część wspólną to wystarczy:
- utworzyć klasę np. MatchService (część wspólna)
- dostarczyć ją do klas Match i NamedMatch (które implementują część wspólną, oraz korzystają z jej funkcjonalności)
Czyli prosta fasada. Dzięki temu możemy dowolnie rozwijać każdą klasę nie martwiąc się, że np. złamiemy kontrakt klasy nadrzędnej. Wiele frameworków ma wręcz zautomatyzowane tworzenie fasad, gdzie fasadę wpisuje się w .cfg i już mamy gotowy do użycia obiekt Match lub NamedMatch wraz ze wszystkimi zależnościami.
lukaskolista
Błędnie założyłeś, że te obiekty są usługami. Kompozycja jest przestarzała? Od kiedy?

Zrobiłem to tak:
MatchInterface -> Match (class)
NamedMatchInterface -> NamedMatch (class)

NamedMatchInterface posiada metody:
getName(): string
getMatch(): MatchInterface
com
@offtop

PSR nie definiuje standardu nazewnictwa klas, interfejsów itd smile.gif

Wspomniane było o tym kiedyś, ale w kontekście nazewnictwa dla samego PSR, a w projekcie można przyjąć sobie jaki chcesz standard, ja ostatnio preferuje bez suffixów, według mnie tak jest lepiej:
  1. class Foo
  2. {
  3. protected $bar;
  4. public function __construct(Bar $bar)
  5. {
  6. $this->bar = $bar;
  7. }
  8. }
  9.  
  10. $foo = new Foo(new SimpleBar());

niż:
  1. class Foo
  2. {
  3. protected $bar;
  4. public function __construct(BarInterface $bar)
  5. {
  6. $this->bar = $bar;
  7. }
  8. }
  9.  
  10. $foo = new Foo(new SimpleBar());

Bo tak jasno mówisz że to jest obiekt danego typu a nie jakiś interfejs
Pyton_000
Widzisz... I tu jest problem smile.gif
Bo wg. Ciebie oczekujesz Obiektu konkretnego typu.
Dodając sufix widzisz że oczekujesz też Typu, ale już widzisz że Obiekt Może być Potter implementujący Interface. Nie mając Sufixu musisz się posiłkować dokumentacją żeby wiedzieć czy to co tam jest to Interface czy konkretny obiekt.

A co jeśli nazwiesz Interface Book, a potem chcesz klasę Book? Bo robisz z niej np. dziedziczenie?

Interface, Trait, abstract to wszystko powinno być dodawane do nazwy.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.