2013-11-26 24 views
8

Dużo czytam na temat wzoru odwiedzającego i jego rzekomych zalet. Dla mnie jednak wydaje się, że nie są aż tak wiele korzyści, gdy stosowane w praktyce:Jakie są rzeczywiste zalety wzoru gościa? Jakie są alternatywy?

  • „Wygodny” i „eleganckie” wydaje się oznaczać wiele, wiele standardowy kod
  • Dlatego kod jest trudny do naśladowania. Również "akceptuj"/"odwiedziny" nie jest zbyt opisowy.
  • Jeszcze gorszy kod na płycie głównej, jeśli w twoim języku programowania nie ma metody przeciążania (np. Vala)
  • Nie możesz ogólnie dodać nowych operacji do istniejącej hierarchii typów bez modyfikacji wszystkich klasy, ponieważ potrzebujesz nowych metod "akceptuj"/"odwiedzaj" wszędzie gdzie jak tylko potrzebujesz operacji z różnymi parametrami i/lub wartością zwracaną (zmiany w klasach w każdym miejscu to jedna rzecz, której ten wzór powinien unikać !?)
  • Dodanie nowego typu do hierarchii typów wymaga wprowadzenia zmian w wszystkich odwiedzających. Ponadto, użytkownicy nie mogą po prostu zignorować typ - trzeba utworzyć pusty metodę wizytowe (boilerplate ponownie)

To wszystko po prostu wydaje się być strasznie dużo pracy, kiedy wszystko, co chcesz zrobić, to rzeczywiście:

// Pseudocode 
int SomeOperation(ISomeAbstractThing obj) { 
    switch (type of obj) { 
     case Foo: // do Foo-specific stuff here 
     case Bar: // do Bar-specific stuff here 
     case Baz: // do Baz-specific stuff here 
     default: return 0; // do some sensible default if type unknown or if we don't care 
    } 
} 

Jedyna prawdziwa korzyść, którą widzę (której nie widziałem w żadnym miejscu): Wzorzec użytkowników jest prawdopodobnie najszybszą metodą implementacji powyższego fragmentu kodu pod względem czasu procesora (jeśli nie masz język z podwójną wysyłką lub sprawnym porównaniem typów w sposób podobny do powyższego pseudokodu).

Pytania:

  • tak, jakie zalety odwiedzający zostały pominięte?
  • Jakie alternatywne koncepcje/struktury danych można zastosować, aby powyższa fikcyjna próbka kodu działała równie szybko?
+0

IMO przegapiłeś ważny punkt ... powinieneś ** NIGDY ** (I napiszę jeszcze większy) mają coś takiego jak "switch (type of obj)". Nie ma znaczenia, czy odwiedzający, czy nie. Jeśli twój kod nie jest taki, to nie napiszesz żadnego kodu standardowego. –

+0

** Przykłady **, kiedy odwiedzający może Ci pomóc? Wyobraź sobie, że piszesz narzędzie do wyszukiwania tekstu w plikach. Wyszukiwarka odwiedza każdy element systemu plików (pliki będą odwiedzane, a katalogi będą propagować odwiedziny do każdego elementu podrzędnego). Elementy systemu plików mogą być katalogami, plikami, linkami do stron FTP ... wyszukiwarka nigdy nie będzie wiedzieć, co działa. Gość nie powinien znać dokładnego typu (nie z powodu wzoru gościa, ale z powodu zasad OOP ...) –

+0

Odwiedzający są na zbyt niskim poziomie. Nie ma sensu korzystać z odwiedzających, gdy możesz zrobić coś takiego: http://www.cs.indiana.edu/~dyb/pubs/nano-jfp.pdf –

Odpowiedz

3

Według mojej osobistej opinii wzór odwiedzający jest przydatny tylko wtedy, gdy interfejs, który chcesz zaimplementować, jest raczej statyczny i niewiele się zmienia, a Ty chcesz dać każdemu szansę na wdrożenie własnej funkcjonalności.

Zauważ, że możesz uniknąć zmiany wszystkiego za każdym razem, gdy dodajesz nową metodę, tworząc nowy interfejs zamiast modyfikować stary - wtedy musisz mieć trochę logiki w obsłudze przypadku, gdy odwiedzający nie implementuje wszystkich interfejsy.

Zasadniczo korzyścią jest to, że pozwala wybrać właściwą metodę wywoływania w czasie wykonywania, a nie w czasie kompilacji - a dostępne metody są faktycznie rozszerzalne.

Aby uzyskać więcej informacji, zajrzyj na ten artykuł - http://rgomes-info.blogspot.co.uk/2013/01/a-better-implementation-of-visitor.html

4

przez doświadczenie, chciałbym powiedzieć, że „Dodawanie nowego typu do hierarchii typu wymaga zmiany dla wszystkich odwiedzających” jest zaletą. Ponieważ zdecydowanie zmusza Cię do rozważenia nowego typu dodanego w WSZYSTKICH miejscach, w których robiłeś coś konkretnego. Zapobiega zapominaniu o ....

+0

To nie zawsze jest wymagane, a po drugie jest to całkowicie sprzeczne z podstawowymi zasadami Open/Closed. –

2

Na miarę widziałem do tej pory istnieją dwa zastosowania/korzyści dla wzorca projektowego gość:

  1. Podwójne wysyłki
  2. odrębne struktury danych z operacjami na nich

Podwójna wysyłka

Załóżmy, że masz klasę pojazdów i klasę VehicleWasher. VehicleWasher ma Wash (Vehicle) metoda:

VehicleWasher 
    Wash(Vehicle) 

Vehicle 

Dodatkowo mamy także konkretne pojazdy jak samochód, aw przyszłości będziemy mieć również inne konkretne pojazdy. Do tego mamy klasę samochodu, ale także specyficzną klasę CarWasher który ma działanie specyficzne dla mycia samochodów (pseudo kierunkowym):

CarWasher : VehicleWasher 
    Wash(Car) 

Car : Vehicle 

następnie rozważyć następujący kod klienta do mycia konkretny pojazd (zauważ, że x i podkładkę deklarowane są za pomocą ich typ bazowy, ponieważ przypadki mogą być tworzone dynamicznie na podstawie danych wprowadzonych przez użytkownika lub zewnętrznych wartości konfiguracyjnych; w tym przypadku są one po prostu stworzony z nowym operatorem chociaż):

Vehicle x = new Car(); 
VehicleWasher washer = new CarWasher(); 

washer.Wash(x); 

Wiele języków wykorzystać pojedynczą przesyłkę zadzwonić odpowiednia funkcja. Pojedyncza wysyłka oznacza, że ​​podczas wykonywania tylko jedna wartość jest brana pod uwagę przy określaniu, która metoda wywołać. Dlatego rozważymy tylko właściwy typ płuczki. Rzeczywisty typ x nie jest brany pod uwagę. Ostatni wiersz kodu wywoła CarWasher.Wash (Pojazd) i NOT CarWasher.Wash (Samochód).

Jeśli używasz języka, który nie obsługuje wielu wysyłek i jest ci potrzebna (mogę szczerze powiedzieć, że nigdy nie spotkałem się z taką sytuacją), możesz użyć wzoru projektu gościa, aby to umożliwić. W tym celu należy zrobić dwie rzeczy. Przede wszystkim dodać metodę Accept do klasy nośnika (visitee), która akceptuje VehicleWasher jako gość, a następnie wywołać jego operacji mycia:

Accept(VehicleWasher washer) 
    washer.Wash(this); 

Drugą rzeczą jest to, aby zmodyfikować kod wywołujący i wymienić podkładkę. Przemyć (x); Linia z następujących czynności:

x.Accept(washer); 

Teraz po wywołaniu metody zaakceptować rzeczywisty typ x jest uważany (i tylko, że x, ponieważ zakładamy, aby być przy użyciu jednego języka wysyłki). W implementacji metody Accept metoda wywoływania jest wywoływana na obiekcie podkładki (odwiedzającym). W tym celu rozważany jest rzeczywisty typ pralki, co spowoduje wywołanie CarWasher.Wash (Car). Łącząc dwie pojedyncze wysyłki realizowana jest podwójna wysyłka.

Teraz, aby wypowiedzieć się na temat uwagi, takich jak Akceptuj i Odwiedzaj i Użytkownicy są bardzo niespecyficzne. To absolutna prawda. Ale nie bez powodu.

Należy wziąć pod uwagę wymóg w tym przykładzie, aby zaimplementować nową klasę, która jest w stanie naprawić pojazdy: pojazd VehicleRepair. Ta klasa może być używana tylko jako gość w tym przykładzie, jeśli dziedziczy po VehicleWasher i ma logikę naprawy wewnątrz metody Wash. Ale to oczywiście nie ma sensu i byłoby mylące.Tak więc całkowicie zgadzam się, że wzorce projektowe mają na ogół bardzo niejasne i niespecyficzne nazwy, ale czynią je odpowiednimi w wielu sytuacjach. Im dokładniejsze jest twoje nazewnictwo, tym bardziej restrykcyjne może być.

Instrukcja przełącznika uwzględnia tylko jeden typ, który w rzeczywistości jest ręcznym sposobem pojedynczej wysyłki. Zastosowanie wzoru wzoru dla odwiedzających w powyższy sposób zapewni podwójną wysyłkę. W ten sposób niekoniecznie potrzebujesz dodatkowych metod odwiedzania podczas dodawania dodatkowych typów do hierarchii. Oczywiście dodaje trochę skomplikowania, ponieważ sprawia, że ​​kod jest mniej czytelny. Ale oczywiście wszystkie wzory mają swoją cenę.

Oczywiście tego wzoru nie można zawsze użyć. Jeśli spodziewasz się wielu skomplikowanych operacji z wieloma parametrami, nie będzie to dobra opcja.

Alternatywą jest użycie języka, który obsługuje wielokrotną wysyłkę. Na przykład .NET nie obsługiwał go do wersji 4.0, która wprowadziła dynamiczne słowo kluczowe. Następnie w C# można wykonać następujące czynności:

washer.Wash((dynamic)x); 

Ponieważ x jest następnie przekształcany do dynamicznego typu jego rzeczywisty typ zostaną zakwalifikowani do wysyłki i tak oba x oraz podkładka zostaną wykorzystane, aby wybrać właściwą metodę tak, że CarWasher.Wash (samochód) zostanie wywołany (sprawdzenie, że kod działa poprawnie i pozostanie intuicyjny).

Osobne struktury danych i operacje

Inną zaletą i wymogiem jest, że można go oddzielić od struktury danych operacji. Może to być zaletą, ponieważ pozwala dodawać nowych użytkowników, którzy mają własne operacje, a także umożliwia dodawanie struktur danych, które "dziedziczą" te operacje. Może być jednak zastosowany tylko wtedy, gdy ta seperacja może być wykonana/ma sens. Klasy, które wykonują operacje (użytkownicy) nie znają struktury danych ani nie muszą wiedzieć, co sprawia, że ​​kod jest łatwiejszy do utrzymania i wielokrotnego użytku. Z tego powodu odwiedzający wykonują operacje dla różnych elementów w strukturach danych.

Załóżmy, że masz różne struktury danych i wszystkie składają się z elementów klasy Element. Struktury mogą być listy, stosy, drzewa, kolejki itp

można następnie wdrożyć odwiedzających że w tym przypadku będzie miał następującą metodę:

Visit(Item) 

Struktury danych muszą przyjąć gości i wtedy nazywamy Odwiedź metodę dla każdej pozycji.

W ten sposób można wdrożyć wszelkiego rodzaju użytkowników i nadal można dodawać nowe struktury danych, o ile składają się one z elementów typu Element.

W przypadku bardziej szczegółowych struktur danych z dodatkowymi elementami (np. Węzłem) można rozważyć określonego użytkownika (NodeVisitor), który dziedziczy po zwykłym Goście i nowe struktury danych akceptują tego użytkownika (Accept (NodeVisitor)). Nowi użytkownicy mogą być wykorzystywani do nowych struktur danych, ale także do starych struktur danych z powodu dziedziczenia, więc nie trzeba modyfikować istniejącego "interfejsu" (w tym przypadku super klasy).

3

To jest stare pytanie, ale chciałbym odpowiedzieć.

Wzorzec odwiedzającego jest przydatny przede wszystkim wtedy, gdy w miejscu, w którym zbudowano drzewo obiektów, jest złożony wzór, a taki układ drzewa jest nieprzewidywalny.

Sprawdzanie typu może być jedną z rzeczy, które może wykonać odwiedzający, ale powiedzmy, że chcesz zbudować wyrażenie na podstawie drzewa, które może mieć różny kształt w zależności od danych wprowadzonych przez użytkownika lub coś w tym rodzaju, że odwiedzający byłby skutecznym sposobem aby zweryfikować drzewo lub zbudować złożony obiekt zgodnie z elementami znajdującymi się na drzewie.

Użytkownik może również nosić obiekt, który robi coś w każdym węźle, który może znaleźć na tym drzewie. gość ten może być samem złożonym, łączącym wiele operacji w każdym węźle lub może przenosić obiekt mediatora w celu pośredniczenia w operacjach lub wywoływania zdarzeń w każdym węźle.

Wyobraźnia jest granicą tego wszystkiego. możesz filtrować kolekcję, budować abstrakcyjne drzewo składniowe z całego drzewa, parsować ciąg znaków, sprawdzać zbiór rzeczy itp.