Czy możemy całkowicie zastąpić dziedziczenie za pomocą wzorca strategii i wstrzykiwania zależności?


10

Na przykład:

var duckBehaviors = new Duckbehavior();
duckBehaviors.quackBehavior = new Quack();
duckBehaviors.flyBehavior = new FlyWithWings();
Duck mallardDuck = new Duck(DuckTypes.MallardDuck, duckBehaviors)

Ponieważ klasa Duck zawiera wszystkie zachowania (abstrakcyjne), tworzenie nowej klasy MallardDuck(która się rozszerza Duck) nie wydaje się być wymagane.

Odniesienie: Wzorzec projektowy Head First, rozdział 1.


Jakie są rodzaje Duckbehavior.quackBehaviori inne pola w twoim kodzie?
max630

W twoim przykładzie nie ma zastrzyku zależności.
David Conrad

11
Dziedziczenie jest niesamowite, gdy jesteś młodszym i średnim programistą, ponieważ istnieje długa historia refaktoryzacji sztuczek i wzorców projektowych. Ale najbardziej doświadczeni programiści, z którymi rozmawiałem, wolą naprawdę płytkie hierarchie dziedziczenia oparte na kompozycji, jeśli to możliwe. Dziedziczenie może powodować ściślejsze sprzężenie niż to konieczne i może utrudniać zmiany.
Mark Rogers


5
@DavidConrad: OP nie robi nowości w klasie . DI / IoC dotyczy wstrzykiwania zależności, a nie kontenerów lub nigdy nie używania „nowego”; to całkowicie ortogonalny problem. Poza tym zawsze musisz gdzieś zmienić kod - może to być katalog główny kompozycji lub jakiś plik konfiguracyjny. Kontrola jest odwrócona tutaj w typie Kaczki, ponieważ rzeczą, która kontroluje tworzenie zależności, nie jest ctor Kaczki, ale jakiś kontekst zewnętrzny; jest to zabawkowy przykład podany dla jasności, doskonale jest, że kontekst zewnętrzny jest reprezentowany tylko przez kod wywołujący.
Filip Milovanović,

Odpowiedzi:


21

Jasne, ale nazywamy ten skład i delegację . Wzorzec strategii i wstrzykiwanie zależności może wydawać się strukturalnie podobne, ale ich zamiary są różne.

Wzorzec strategii pozwala modyfikować zachowanie w czasie wykonywania w ramach tego samego interfejsu. Mógłbym polecić kaczce krzyżówki latać i patrzeć, jak leci skrzydłami. Następnie zamień go na kaczkę odrzutowca i obserwuj, jak leci liniami Delta. Wykonanie tego podczas działania programu jest kwestią Wzorca Strategii.

Wstrzykiwanie zależności jest techniką pozwalającą uniknąć zależności kodowania na sztywno, dzięki czemu mogą one zmieniać się niezależnie, bez konieczności modyfikacji klientów po ich zmianie. Klienci po prostu wyrażają swoje potrzeby, nie wiedząc, w jaki sposób zostaną zaspokojone. Tak więc sposób ich spełnienia jest ustalany gdzie indziej (zazwyczaj głównie). Nie potrzebujesz dwóch kaczek, aby skorzystać z tej techniki. Po prostu coś, co korzysta z kaczki, nie wiedząc ani nie dbając o nią. Coś, co nie buduje kaczki lub nie szuka jej, ale jest całkowicie zadowolone z użycia dowolnej kaczki, którą ci podajesz.

Jeśli mam konkretną klasę kaczki, mogę ją wprowadzić w życie. Mógłbym nawet zmienić zachowanie z latania ze skrzydłami na latanie z deltą w oparciu o zmienną stanu. Że zmienna może być logiczna, int, lub może być FlyBehavior, że ma flymetodę, która robi co mi latający styl bez konieczności przetestowania go if. Teraz mogę zmieniać style latania bez zmiany typów kaczek. Teraz Krzyżówki mogą zostać pilotami. To jest skład i delegacja . Kaczka składa się z FlyBehavior i może przekazywać jej prośby o latanie. Możesz w ten sposób zastąpić wszystkie zachowania kaczki na raz lub zatrzymać coś dla każdego zachowania lub dowolnej kombinacji pomiędzy.

To daje ci te same moce, które ma dziedzictwo, z wyjątkiem jednej. Dziedziczenie pozwala wyrazić metody kaczki, które zastępujesz w podtypach kaczki. Skład i delegowanie wymaga od Kaczki jawnego delegowania do podtypów od samego początku. Jest to o wiele bardziej elastyczne, ale wymaga więcej pisania na klawiaturze i Duck musi wiedzieć, że to się dzieje.

Jednak wiele osób uważa, że ​​od samego początku należy wyraźnie przewidzieć dziedziczenie. A jeśli tak nie było, należy oznaczyć swoje klasy jako zapieczętowane / końcowe, aby nie dopuścić do dziedziczenia. Jeśli weźmiesz ten pogląd, dziedziczenie naprawdę nie ma przewagi nad kompozycją i delegowaniem. Ponieważ w obu przypadkach musisz albo zaprojektować rozszerzalność od samego początku, albo być gotowym na później.

Burzenie rzeczy jest w rzeczywistości popularną opcją. Pamiętaj tylko, że w niektórych przypadkach jest to problem. Jeśli niezależnie wdrożyłeś biblioteki lub moduły kodu, których nie zamierzasz aktualizować w następnej wersji, możesz skończyć z wersjami klas, które nie wiedzą nic o tym, co robisz.

Chociaż chęć późniejszego zniszczenia może uwolnić cię od nadmiernego projektowania, jest coś bardzo potężnego w tym, że możesz zaprojektować coś, co używa kaczki, bez konieczności dowiedzenia się, co kaczka faktycznie zrobi, gdy zostanie użyta. To niewiedza jest potężną rzeczą. Pozwala ci na chwilę przestać myśleć o kaczkach i pomyśleć o reszcie kodu.

„Czy możemy” i „powinniśmy” to różne pytania. Preferuj kompozycję zamiast dziedziczenia nie oznacza, że ​​nigdy nie używaj dziedziczenia. Nadal istnieją przypadki, w których dziedziczenie jest najbardziej sensowne. Pokażę ci mój ulubiony przykład :

public class LoginFailure : System.ApplicationException {}

Dziedziczenie pozwala tworzyć wyjątki z bardziej szczegółowymi, opisowymi nazwami w jednym wierszu.

Spróbuj zrobić to z kompozycją, a dostaniesz bałagan. Ponadto nie ma ryzyka wystąpienia problemu jo-jo, ponieważ nie ma tu danych ani metod umożliwiających ponowne użycie i zachęcanie do tworzenia łańcucha spadkowego. Wszystko to dodaje dobre imię. Nigdy nie lekceważ wartości dobrego imienia.


1
Nigdy nie lekceważ wartości dobrego imienia ”. Czas na nowe ubrania cesarza: LoginExceptionnie jest to dobre imię. To klasyczne „nazywanie smerfa”, a jeśli go zabiorę Exception, wszystko, co mam Login, to nie mówi mi nic o tym, co poszło nie tak.
David Arno,

Niestety, utknęliśmy w absurdalnej konwencji umieszczania Exceptionna końcu każdej klasy wyjątkowej, ale proszę nie mylić „utknąłem z nią” z „dobrym”.
David Arno,

@DavidArno Jeśli możesz zasugerować lepsze imię, jestem cały w uszach. Mamy tę zaletę, że nie jesteśmy uwięzieni w konwencjach istniejącej bazy kodu. Gdybyś miał moc zmieniania świata jednym palcem, jak byś to nazwał?
candied_orange

Chciałbym wymienić je, StackOverflow, OutOfMemory, NullReferenceAccess, LoginFailureitd. Zasadniczo, wziąć „wyjątek” od nazwy. W razie potrzeby napraw je tak, aby opisywały, co poszło nie tak.
David Arno,

@DavidArno według twojego polecenia.
candied_orange

7

Możesz zastąpić prawie każdą metodologię inną metodologią i nadal produkować działające oprogramowanie. Jednak niektóre lepiej pasują do konkretnego problemu niż inne.

To zależy od wielu rzeczy, które są lepsze. Stan techniki w stosowaniu, doświadczenie w zespole, oczekiwany rozwój w przyszłości, osobiste preferencje i to, jak trudno będzie nowemu przybyszowi, aby się nim zająć, by wymienić tylko kilka.

Gdy zdobędziesz więcej doświadczenia i będziesz częściej zmagał się z dziełami innych ludzi, prawdopodobnie położysz większy nacisk na ostatnią kontemplację.

Dziedziczenie jest nadal ważnym i silnym narzędziem do modelowania, które niekoniecznie zawsze jest najbardziej elastyczne, ale oferuje silne wskazówki dla nowych ludzi, którzy mogą być wdzięczni za wyraźne mapowanie do dziedziny problemów.


-1

Dziedzictwo nie jest tak ważne, jak kiedyś sądzono. Jest to nadal ważne , a usunięcie go byłoby poważnym błędem.

Skrajnym przykładem jest Objective-C, gdzie wszystko jest podklasą NSObject (kompilator faktycznie nie pozwala zadeklarować klasy, która nie ma klasy podstawowej, więc wszystko , co nie jest wbudowane w kompilator, musi być podklasą czegoś wbudowane w kompilator). Istnieje wiele przydatnych rzeczy wbudowanych w NSObject, których nie musisz przegapić.

Innym interesującym przykładem jest UIView, podstawowa klasa „view” w programowaniu UIKit na iOS. Jest to klasa, która z jednej strony przypomina trochę abstrakcyjną klasę deklarującą funkcjonalność, którą podklasy mają wypełnić, ale jest także przydatna sama w sobie. Ma podklasy dostarczane przez UIKit, które są często używane jako takie. Kompozycja programisty instaluje widoki podrzędne w widoku. Często istnieją podklasy zdefiniowane przez programistów, które często wykorzystują kompozycję. Nie ma jednej ścisłej reguły, a nawet reguł, używasz tego, co najlepiej pasuje do twoich wymagań.


Twój „skrajny przykład” jest mniej więcej taki, jak działa większość współczesnych języków OO: podklasy Pythona type, każda dziedzina, która nie jest prymitywna w Javie, dziedziczy Objectpo prototypie Objectitd.
Delioth

-1

[Zaryzykuję bezczelną odpowiedź na tytułowe pytanie.]

Czy możemy całkowicie zastąpić dziedziczenie za pomocą wzorca strategii i wstrzykiwania zależności?

Tak ... z wyjątkiem tego, że sam wzorzec strategii wykorzystuje dziedziczenie. Wzorzec strategii działa na dziedziczeniu interfejsu. Zastąpienie dziedziczenia kompozycją + strategią przenosi dziedziczenie w inne miejsce. Niemniej jednak takie zastąpienie często jest warte wykonania, ponieważ pozwala na podrażnianie hierarchii .


2
Wzorzec strategii działa na dziedziczeniu interfejsu ”. Pamiętaj, że wzór strategii jest wzorem projektu; nie wzorzec implementacji. Można go więc zaimplementować za pomocą interfejsów, ale można go również zaimplementować np. Za pomocą skrótu / słownika funkcji.
David Arno,

3
Dziedziczenie interfejsu wcale nie jest dziedziczeniem, jest tylko rodzajem umowy lub klasyfikatora. Możesz użyć delegatów również do wzorca strategii (wolę ich używać).
Deepak Mishra

Plus jeden, koleś: ... działa na dziedziczeniu interfejsu , hołdy na właściwym użyciu ogólnego terminu „interfejs”. Myślę, że zły lub niekompetentny projekt klasy jest podstawowym powodem, dla którego pojawia się to pytanie. Wyjaśnić, dlaczego / jak wziąć książkę; diabeł tkwi w szczegółach. Pracowałem z projektami dziedziczenia, które były przyjemnością, a jednocześnie głębokim dziedzictwem, które było piekłem. Widziałem interfaceprojekt craptastic z dziedziczeniem. Ukrytym problemem tutaj jest niespójna implementacja zachowań zależnych od morfingu. P, S. dziedziczenie i interfejs nie wykluczają się wzajemnie,
radarbob

@DavidArno komentarz : Ten komentarz byłby w porządku, gdyby był dołączony do pytania OP. Pytanie OP jest technicznie błędnie sformułowane, ale wciąż je otrzymujemy. Problemem nie jest ta konkretna odpowiedź.
radarbob

@DeepakMishra komentarz: . „Interfejs” to wszyscy publiczni członkowie klasy. Niestety znaczenie „interfejs” jest przeciążone. Musimy uważać, aby odróżnić ogólne znaczenie słowem kluczowym języka programowaniainterface
radarbob,

-2

Nie. Jeśli kaczka krzyżówka wymaga innych parametrów do wystąpienia niż jakikolwiek inny rodzaj kaczki, zmiana koszmaru byłaby koszmarem. Czy powinieneś być również w stanie utworzyć kaczkę? To bardziej kaczka krzyżówka, kaczka mandarynka lub jakikolwiek inny rodzaj kaczki.

Mogę też potrzebować trochę logiki wokół typów, aby hierarchia typów mogła być lepsza.

Teraz, jeśli ponowne użycie kodu staje się problematyczne dla niektórych funkcji, możemy je skomponować, przekazując funkcje przez konstruktor.

Ponownie, to naprawdę zależy od twojego przypadku użycia. Jeśli masz tylko kaczkę i kaczkę krzyżówkę, hierarchia klas jest znacznie prostszym rozwiązaniem.

Ale oto przykład, w którym chcesz zastosować wzorzec strategii: jeśli masz klasy klientów i chcesz przekazać strategię rozliczeniową (interfejs), która może być strategią rozliczeń Happy Hour lub zwykłą strategią rozliczeń, możesz ją przekazać w konstruktorze. W ten sposób nie musisz tworzyć dwóch różnych klas klientów i nie potrzebujesz hierarchii. Masz tylko jedną klasę klienta.

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.