Wzorce projektowe, których należy unikać [zamknięte]

105

Wiele osób wydaje się zgadzać, że wzorzec Singleton ma szereg wad, a niektórzy nawet sugerują całkowite jego unikanie. Tutaj jest doskonała dyskusja . Wszelkie uwagi dotyczące wzorca Singleton prosimy kierować do tego pytania.

Moje pytanie : czy istnieją inne wzorce projektowe, których należy unikać lub których należy używać z wielką ostrożnością?

Brian Rasmussen
źródło
Muszę tylko zwrócić uwagę na świetną listę Design Antipatterns deviq.com/antipatterns
Developer
@casperOne, dlaczego zamknąłeś pytanie? Pytanie było uzasadnione.
bleepzter

Odpowiedzi:

149

Wzory są złożone

Wszystkie wzorce projektowe należy stosować ostrożnie. Moim zdaniem powinieneś dokonać refaktoryzacji w kierunku wzorców, gdy istnieje ważny powód, aby to zrobić, zamiast natychmiast wdrażać wzorzec. Ogólny problem z używaniem wzorców polega na tym, że dodają one złożoności. Nadużywanie wzorców utrudnia dalsze rozwijanie i utrzymywanie danej aplikacji lub systemu.

W większości przypadków istnieje proste rozwiązanie i nie ma potrzeby stosowania żadnego konkretnego wzoru. Dobrą praktyczną zasadą jest użycie wzorca, gdy fragmenty kodu są zwykle zastępowane lub wymagają częstych zmian, i należy być przygotowanym na przyjęcie zastrzeżenia złożonego kodu podczas korzystania z wzorca.

Pamiętaj, że Twoim celem powinna być prostota i stosować wzorzec, jeśli widzisz praktyczną potrzebę wprowadzenia zmian w kodzie.

Zasady ponad wzorami

Używanie wzorców może wydawać się sporne, jeśli mogą one ewidentnie prowadzić do zbyt zaawansowanych i skomplikowanych rozwiązań. Jednak zamiast tego znacznie bardziej interesujące dla programisty jest zapoznanie się z technikami i zasadami projektowania, które stanowią podstawę większości wzorców. W rzeczywistości jedna z moich ulubionych książek o „wzorcach projektowych” podkreśla to , powtarzając, jakie zasady mają zastosowanie do danego wzoru. Są na tyle proste, że pod względem trafności mogą być przydatne niż wzorce. Niektóre zasady są na tyle ogólne, że obejmują coś więcej niż programowanie obiektowe (OOP), takie jak Zasada Zastępowania Liskova , o ile można budować moduły swojego kodu.

Istnieje wiele zasad projektowania, ale te opisane w pierwszym rozdziale książki GoF są całkiem przydatne na początku.

  • Programuj jako „interfejs”, a nie „implementację”. (Gang of Four 1995: 18)
  • Preferuj „kompozycję obiektów” zamiast „dziedziczenia klas”. (Gang of Four 1995: 20)

Pozwól tym na chwilę zagłębić się w ciebie Należy zauważyć, że gdy napisano GoF, interfejs oznacza wszystko, co jest abstrakcją (co oznacza również superklasy ), czego nie należy mylić z interfejsem jako typem w Javie lub C #. Druga zasada pochodzi z obserwowanego nadużywania dziedziczenia, które niestety jest nadal powszechne .

Stamtąd możesz przeczytać o zasadach SOLID, które zostały ujawnione przez Roberta Cecila Martina (aka. Wujek Bob) . Scott Hanselman przeprowadził wywiad z wujkiem Bobem w podcastu na temat tych zasad :

  • S Ingle odpowiedzialności Zasada
  • O pen Zamknięta Zasada
  • Zasada podstawienia L iskova
  • I nterface segregacja Principle
  • D ependency Zasada Inwersja

Te zasady są dobrym początkiem do czytania i dyskusji z rówieśnikami. Może się okazać, że zasady przeplatają się ze sobą iz innymi procesami, takimi jak oddzielenie obaw i wstrzyknięcie zależności . Po pewnym czasie wykonywania TDD może się również okazać, że zasady te przychodzą naturalnie w praktyce, ponieważ trzeba do pewnego stopnia ich przestrzegać, aby stworzyć izolowane i powtarzalne testy jednostkowe.

Spoike
źródło
7
+1 Bardzo dobra odpowiedź. Wydaje się, że każdy (początkujący) programista zna dziś swoje wzorce projektowe lub przynajmniej wie, że istnieją. Ale bardzo wielu nigdy nie słyszało o niektórych absolutnie podstawowych zasadach, takich jak Pojedyncza odpowiedzialność, nie mówiąc już o stosowaniu, do zarządzania złożonością ich kodu.
eljenso
21

Tym, o co najbardziej martwili się sami autorzy Design Patterns, był wzorzec „Visitor”.

To „zło konieczne” - ale często jest nadużywane, a potrzeba jego użycia często ujawnia bardziej fundamentalną wadę w Twoim projekcie.

Alternatywną nazwą dla wzorca „Odwiedzający” jest „Wiele wysyłek”, ponieważ wzorzec gościa jest tym, co otrzymujesz, gdy chcesz użyć jednego typu języka OO wysyłki do wybrania kodu do użycia na podstawie typu dwóch (lub więcej) różnych obiektów.

Klasycznym przykładem jest przecięcie między dwoma kształtami, ale jest jeszcze prostszy przypadek, który jest często pomijany: porównanie równości dwóch heterogenicznych obiektów.

W każdym razie często kończy się coś takiego:

interface IShape
{
    double intersectWith(Triangle t);
    double intersectWith(Rectangle r);
    double intersectWith(Circle c);
}

Problem polega na tym, że połączyłeś wszystkie swoje implementacje "IShape". Zasugerowałeś, że za każdym razem, gdy chcesz dodać nowy kształt do hierarchii, będziesz musiał zmienić również wszystkie inne implementacje „kształtu”.

Czasami jest to poprawny minimalny projekt - ale przemyśl to. Czy Twój projekt naprawdę wymaga wysyłki na dwa typy? Czy chcesz napisać każdą kombinatoryczną eksplozję multimetod?

Często, wprowadzając inną koncepcję, możesz zmniejszyć liczbę kombinacji, które będziesz musiał napisać:

interface IShape
{
    Area getArea();
}

class Area
{
    public double intersectWith(Area otherArea);
    ...
}

Oczywiście to zależy - czasami naprawdę musisz napisać kod, aby poradzić sobie z tymi wszystkimi różnymi przypadkami - ale warto zrobić sobie przerwę i pomyśleć przed podjęciem decyzji i użyciem Visitora. To może zaoszczędzić ci później dużo bólu.

Paul Hollingsworth
źródło
2
Mówiąc o odwiedzających, wujek Bob używa go „cały czas” butunclebob.com/ArticleS.UncleBob.IuseVisitor
Spoike
3
@Paul Hollingsworth Czy możesz podać odniesienie do tego, gdzie jest napisane, że autorzy wzorców projektowych są zmartwieni (i dlaczego się martwią)?
m3th0dman
16

Singletony - klasa używająca singletona X ma zależność od niej, która jest trudna do zauważenia i trudna do wyodrębnienia na potrzeby testowania.

Są używane bardzo często, ponieważ są wygodne i łatwe do zrozumienia, ale mogą naprawdę skomplikować testowanie.

Zobacz: Singletony są patologicznymi kłamcami .

orip
źródło
1
Mogą również ułatwić testowanie, ponieważ mogą dać ci pojedynczy punkt do wstrzyknięcia obiektu pozorowanego. Wszystko sprowadza się do uzyskania właściwej równowagi.
Martin Brown,
1
@Martin: Jeśli oczywiście można zmienić singelton do testowania (jeśli nie używasz standardowej implementacji singeltona), ale czy jest to łatwiejsze niż przekazanie implementacji testowej w konstruktorze?
orip
14

Uważam, że wzorzec Metody Szablonowej jest ogólnie bardzo niebezpiecznym wzorcem.

  • Wiele razy wykorzystuje twoją hierarchię dziedziczenia z „niewłaściwych powodów”.
  • Klasy bazowe mają tendencję do zaśmiecania wszelkiego rodzaju niepowiązanego kodu.
  • Zmusza cię to do zablokowania projektowania, często na dość wczesnym etapie procesu tworzenia. (W wielu przypadkach przedwczesne blokowanie)
  • Zmiana tego na późniejszym etapie staje się coraz trudniejsza.
krosenvold
źródło
2
Dodałbym, że za każdym razem, gdy używasz metody szablonowej, prawdopodobnie lepiej jest używać strategii. Problem z TemplateMethod polega na tym, że między klasą bazową a klasą pochodną występuje ponowne wejście na serwer, co często jest nadmiernie powiązane.
Paul Hollingsworth,
5
@Paul: Metoda szablonowa jest świetna, gdy jest używana prawidłowo, tj. Gdy części, które się różnią, muszą dużo wiedzieć o częściach, które nie są. Uważam, że strategia powinna być stosowana, gdy kod podstawowy wywołuje tylko kod niestandardowy, a metoda szablonu powinna być używana, gdy kod niestandardowy z natury musi wiedzieć o kodzie podstawowym.
dsimcha
tak dsimcha, zgadzam się ... o ile projektant klasy jest tego świadomy.
Paul Hollingsworth
9

Nie sądzę, abyś unikał wzorców projektowych (DP) i nie uważam, że powinieneś zmuszać się do korzystania z DP podczas planowania architektury. Powinniśmy używać PR tylko wtedy, gdy wynikają one w sposób naturalny z naszego planowania.

Jeśli od początku określimy, że chcemy korzystać z danego DP, na wiele naszych przyszłych decyzji projektowych będzie miał wpływ ten wybór, bez gwarancji, że wybrany przez nas DP jest dostosowany do naszych potrzeb.

Nie powinniśmy też traktować DP jako niezmiennej istoty, powinniśmy dostosować wzorzec do naszych potrzeb.

Podsumowując, uważam, że nie powinniśmy unikać DP, powinniśmy je przyjąć, gdy już nabierają kształtu w naszej architekturze.

Megacan
źródło
7

Myślę, że moduł Active Record to nadużywany wzorzec, który zachęca do mieszania logiki biznesowej z kodem trwałości. Nie radzi sobie zbyt dobrze z ukrywaniem implementacji pamięci masowej w warstwie modelu i wiąże modele z bazą danych. Istnieje wiele alternatyw (opisanych w PoEAA), takich jak Table Data Gateway, Row Data Gateway i Data Mapper, które często zapewniają lepsze rozwiązanie iz pewnością pomagają zapewnić lepszą abstrakcję do przechowywania. Ponadto model nie powinien być przechowywany w bazie danych; co z przechowywaniem ich w formacie XML lub uzyskiwaniem do nich dostępu za pomocą usług internetowych? Jak łatwo byłaby zmienić mechanizm przechowywania Twoich modeli?

To powiedziawszy, moduł Active Record nie zawsze jest zły i jest idealny do prostszych aplikacji, w których inne opcje byłyby przesadą.

Tim Wardle
źródło
1
Niby prawda, ale w pewnym stopniu zależna od realizacji.
Mike Woodhouse
6

To proste ... unikaj wzorców projektowych, które nie są dla Ciebie jasne lub takie, w których nie czujesz się komfortowo .

Aby wymienić niektóre ...

istnieją pewne niepraktyczne wzorce , jak np .:

  • Interpreter
  • Flyweight

są też trudniejsze do uchwycenia , jak np .:

  • Abstract Factory - Pełny abstrakcyjny wzór fabryczny z rodzinami utworzonych obiektów nie jest taki prosty, jak się wydaje
  • Bridge - Może stać się zbyt abstrakcyjny, jeśli abstrakcja i implementacja są podzielone na poddrzewa, ale w niektórych przypadkach jest to bardzo użyteczny wzorzec
  • Visitor - Zrozumienie mechanizmu podwójnej wysyłki jest naprawdę MUSI

i jest kilka wzorców, które wyglądają strasznie prosto , ale nie są tak jednoznaczne z różnych powodów związanych z ich zasadą lub realizacją:

  • Singleton - niezbyt zły wzór, po prostu ZBYT nadużywany (często tam, gdzie nie jest odpowiedni)
  • Observer - świetny wzorzec ... po prostu sprawia, że ​​kod jest znacznie trudniejszy do odczytania i debugowania
  • Prototype - kompilator transakcji sprawdza dynamikę (która może być dobra lub zła ... zależy)
  • Chain of responsibility - zbyt często po prostu wymuszone / sztucznie wciskane w projekt

Dla tych „niepraktycznych” warto się dobrze zastanowić przed ich użyciem, bo zazwyczaj jest gdzieś bardziej eleganckie rozwiązanie.

Dla tych „trudniejszych do uchwycenia”… są naprawdę bardzo pomocne, gdy są używane w odpowiednich miejscach i dobrze wdrożone… ale koszmarne, gdy są niewłaściwie używane.

A teraz co dalej ...

Marcel Toth
źródło
Wzór masy muchowej jest koniecznością zawsze, gdy używasz zasobu, często obrazu, więcej niż raz. To nie jest wzór, to rozwiązanie.
Cengiz Kandemir,
5

Mam nadzieję, że nie będę za to zbytnio bić. Christer Ericsson napisał dwa artykuły ( jeden , dwa ) na temat wzorców projektowych w swoim blogu dotyczącym wykrywania kolizji w czasie rzeczywistym . Jego ton jest raczej szorstki i może trochę prowokacyjny, ale mężczyzna zna się na rzeczy, więc nie odrzuciłbym tego jako bredzeń wariata.

falstro
źródło
Ciekawe lektury. Dzięki za linki!
Bombe
3
Kretyni tworzą zły kod. Czy kretyni z wzorami wytwarzają gorszy kod niż kretyni, którzy nigdy nie widzieli wzorców? Myślę, że nie. Inteligentnym ludziom wzorce zapewniają dobrze znane słownictwo, które ułatwia wymianę pomysłów. Rozwiązanie: naucz się wzorców i radzić sobie tylko z inteligentnymi programistami.
Martin Brown
Nie sądzę, aby prawdziwy kretyn mógł stworzyć gorszy kod - bez względu na to, jakiego narzędzia używa
1800 INFORMACJE
1
Myślę, że jego przykład z jego testu w college'u tylko dowodzi, że ludzie, którzy pogardzają swoją domeną problemową i niechęć do studiowania jej przez więcej niż kilka godzin w ciągu jednego weekendu, będą dawać nieprawidłowe odpowiedzi, próbując rozwiązać problemy.
scriptocalypse
5

Niektórzy mówią, że lokalizator usług to anty-wzorzec.

Arnis Lapsa
źródło
Należy również pamiętać, że czasami potrzebny jest lokalizator usług. Na przykład, gdy nie masz odpowiedniej kontroli nad tworzeniem instancji obiektu (np. Atrybuty z parametrami niestałymi w C #). Ale możliwe jest również użycie lokalizatora usług Z wtryskiem ctor.
Sinaesthetic,
2

Uważam, że wzorzec obserwatora ma wiele do odpowiedzenia, działa w bardzo ogólnych przypadkach, ale gdy systemy stają się bardziej złożone, staje się koszmarem, wymagającym powiadomień OnBefore (), OnAfter () i często wysyłania asynchronicznych zadań, aby uniknąć ponownego zachwyt. Znacznie lepszym rozwiązaniem jest opracowanie systemu automatycznej analizy zależności, który instrumentuje dostęp do wszystkich obiektów (z barierami odczytu) podczas obliczeń i automatycznie tworzy krawędź na wykresie zależności.

Jesse Pepper
źródło
4
Zrozumiałem wszystko w Twojej odpowiedzi, aż do słowa „A”
1800 INFORMACJA
Może być konieczne rozwinięcie lub łącze do tej automatycznej analizy zależności, o której mówisz. Również w .NET delegatów / zdarzeń używa się zamiast wzorca obserwatora.
Spoike
3
@Spoike: delegaci / wydarzenia są implementacją wzorca obserwatora
orip
1
Mój osobisty żal do Observera jest taki, że może powodować wycieki pamięci w językach ze śmieciami. Kiedy skończysz z przedmiotem, musisz pamiętać, że obiekt nie zostanie wyczyszczony.
Martin Brown,
@orip: tak, dlatego używasz delegatów / wydarzeń. ;)
Spoike
2

Uzupełnienie postu Spoike'a, Refaktoryzacja do wzorców to dobra lektura.

Adeel Ansari
źródło
W rzeczywistości podałem link do katalogu książki w Internecie. :)
Spoike
O! Nie zawracałem sobie głowy unoszeniem go. Właściwie zaraz po skończeniu pytania przyszła mi do głowy ta książka i wtedy zobaczyłem twoją odpowiedź. Nie mogłem się powstrzymać przed opublikowaniem tego. :)
Adeel Ansari,
0

Iterator to jeszcze jeden wzorzec GoF, którego należy unikać lub przynajmniej używać go tylko wtedy, gdy żadna alternatywa nie jest dostępna.

Alternatywy to:

  1. pętla for-each. Ta konstrukcja jest obecna w większości języków głównego nurtu i może być używana w celu uniknięcia iteratorów w większości przypadków.

  2. selektory à la LINQ lub jQuery. Należy ich używać, gdy for-each nie jest odpowiednie, ponieważ nie wszystkie obiekty z kontenera powinny być przetwarzane. W przeciwieństwie do iteratorów, selektory pozwalają w jednym miejscu wskazać, jakie obiekty mają być przetwarzane.

Volodymyr Frolov
źródło
Zgadzam się z selektorami. Foreach jest iteratorem, większość języków obiektowych udostępnia iterowalny interfejs, który można zaimplementować, aby umożliwić foreach.
Neil Aitken
W niektórych językach konstrukcja for-each może być implementowana za pomocą iteratorów, ale koncepcja jest w rzeczywistości bardziej zaawansowana i bliższa selektorom. W przypadku korzystania z for-each deweloper wyraźnie deklaruje, że wszystkie elementy z kontenera powinny być przetwarzane.
Volodymyr Frolov
Iteratory to świetny wzór. Anty-wzorzec implementowałby IEnumerable / IEnumerator bez iteratora. Wierzę, że LINQ było możliwe dzięki yielditeratorowi. Eric White prowadzi świetną dyskusję na ten temat w C # 3.0: blogs.msdn.com/b/ericwhite/archive/2006/10/04/… . Zapoznaj się również z dyskusją Jeremy'ego Liknessa na temat programów z iteratorami: wintellect.com/CS/blogs/jlikness/archive/2010/03/23/… .
@Ryan Riley, iteratory są obiektami niskiego poziomu i dlatego należy ich unikać w projektowaniu i kodzie wysokiego poziomu. Szczegóły implementacji iteratorów i różnego rodzaju selektorów nie mają tutaj znaczenia. Selektory, w przeciwieństwie do Iteratorów, pozwalają programistom na wyraźne wyrażanie tego, co chcą przetwarzać, i tak, aby były na wysokim poziomie.
Volodymyr Frolov
Fwiw, alternatywna składnia F # podobna do LINQ to `List.map (fun x -> x.Value) xs`, która jest prawie tak długa, jak składnia listy.