W blogach wciąż widuję odniesienia do schematu odwiedzających, ale muszę przyznać, że po prostu tego nie rozumiem. Przeczytałem artykuł w Wikipedii dotyczący tego wzoru i rozumiem jego mechanikę, ale nadal nie jestem pewien, kiedy go użyję.
Jako ktoś, kto niedawno naprawdę wziął wzór dekoratora i teraz widzi go do użycia absolutnie wszędzie, chciałbym móc naprawdę intuicyjnie zrozumieć ten pozornie przydatny wzór.
design-patterns
visitor-pattern
George Mauer
źródło
źródło
Odpowiedzi:
Nie jestem zbyt zaznajomiony ze schematem Visitor. Zobaczmy, czy dobrze to zrozumiałem. Załóżmy, że masz hierarchię zwierząt
(Załóżmy, że jest to złożona hierarchia z dobrze ustalonym interfejsem).
Teraz chcemy dodać do hierarchii nową operację, a mianowicie chcemy, aby każde zwierzę wydało dźwięk. O ile hierarchia jest tak prosta, możesz to zrobić z prostym polimorfizmem:
Ale postępując w ten sposób, za każdym razem, gdy chcesz dodać operację, musisz zmodyfikować interfejs dla każdej pojedynczej klasy hierarchii. Teraz załóżmy, że jesteś zadowolony z oryginalnego interfejsu i chcesz wprowadzić jak najmniej modyfikacji.
Wzorzec gościa pozwala przenieść każdą nową operację do odpowiedniej klasy, a interfejs hierarchii należy rozszerzyć tylko raz. Zróbmy to. Najpierw definiujemy operację abstrakcyjną (klasę „Visitor” w GoF ), która ma metodę dla każdej klasy w hierarchii:
Następnie modyfikujemy hierarchię, aby akceptować nowe operacje:
Wreszcie, wdrażamy rzeczywistą operację, nie zmieniając ani kota, ani psa :
Teraz możesz dodawać operacje bez modyfikowania hierarchii. Oto jak to działa:
źródło
letsDo(Operation *v)
potrzebuje wskaźnika.theSound.hereIsACat(c)
czy wykonałbyś tę pracę, jak uzasadniasz wszystkie koszty ogólne wprowadzone przez wzorzec? podwójna wysyłka jest uzasadnieniem.Przyczyną twojego pomieszania jest prawdopodobnie to, że Gość jest fatalnym błędem. Wielu (wybitnych 1 !) Programistów potknęło się o ten problem. W rzeczywistości implementuje podwójne wysyłanie w językach, które nie obsługują go natywnie (większość z nich nie obsługuje).
1) Moim ulubionym przykładem jest Scott Meyers, uznany autor „Effective C ++”, który nazwał to jedno ze swoich najważniejszych C ++ aha! chwile kiedykolwiek .
źródło
switch
:switch
twardym kodowaniem decyzji po stronie klienta (duplikacja kodu) i nie oferuje statycznego sprawdzania typu ( sprawdź kompletność i odrębność spraw itp.). Wzorzec gościa jest weryfikowany przez moduł sprawdzania typu i zwykle upraszcza kod klienta.virtual
podobne funkcje są tak przydatne w nowoczesnych językach programowania - są podstawowym składnikiem rozszerzalnych programów - moim zdaniem sposób c (zagnieżdżony przełącznik lub dopasowanie wzorców itp. W zależności od wybranego języka) jest znacznie bardziej przejrzysty w kodzie, który nie musi być rozszerzalny, i byłem mile zaskoczony widząc ten styl w skomplikowanym oprogramowaniu, takim jak prover 9. Co ważniejsze, każdy język, który chce zapewnić rozszerzalność, powinien prawdopodobnie uwzględniać lepsze wzorce wysyłania niż rekurencyjne pojedyncze wysyłanie (tj. gość).Wszyscy tutaj mają rację, ale myślę, że nie odnosi się do „kiedy”. Po pierwsze, z wzorców projektowych:
Pomyślmy teraz o prostej hierarchii klas. Mam klasy 1, 2, 3 i 4 oraz metody A, B, C i D. Układaj je jak w arkuszu kalkulacyjnym: klasy to linie, a metody to kolumny.
Obecnie projekt zorientowany obiektowo zakłada, że istnieje większe prawdopodobieństwo, że wyhodujesz nowe klasy niż nowe metody, więc dodanie większej liczby linii, że tak powiem, jest łatwiejsze. Po prostu dodajesz nową klasę, określasz różnice w tej klasie i dziedziczy resztę.
Czasami jednak klasy są względnie statyczne, ale trzeba często dodawać więcej metod - dodając kolumny. Standardowym sposobem w projekcie OO byłoby dodanie takich metod do wszystkich klas, co może być kosztowne. Wzorzec Visitor ułatwia to.
Nawiasem mówiąc, jest to problem, który zamierza rozwiązać wzór Scali.
źródło
Visitor wzornictwo działa bardzo dobrze dla „cyklicznych” struktur, takich jak drzewa katalogów, struktury XML lub konturów dokumentów.
Obiekt Visitor odwiedza każdy węzeł w strukturze rekurencyjnej: każdy katalog, każdy znacznik XML, cokolwiek. Obiekt Visitor nie przechodzi przez strukturę. Zamiast tego metody Visitor są stosowane do każdego węzła struktury.
Oto typowa struktura węzła rekurencyjnego. Może to być katalog lub znacznik XML. [Jeśli jesteś osobą Java, wyobraź sobie wiele dodatkowych metod budowania i utrzymywania listy dzieci.]
visit
Metoda dotyczy obiektu odwiedzających do każdego węzła w strukturze. W tym przypadku jest to gość odgórny. Możesz zmienić strukturęvisit
metody, aby wykonać oddolne lub inne uporządkowanie.Oto superklasa dla odwiedzających. Jest używany przez
visit
metodę. „Dociera” do każdego węzła w strukturze. Ponieważvisit
metoda wywołujeup
idown
, użytkownik może śledzić głębokość.Podklasa może wykonywać takie czynności, jak zliczanie węzłów na każdym poziomie i gromadzenie listy węzłów, generując niezłą hierarchiczną liczbę sekcji ścieżek.
Oto aplikacja. To buduje strukturę drzewa,
someTree
. TworzyVisitor
,dumpNodes
.Następnie stosuje się
dumpNodes
do drzewa.dumpNode
Obiekt będzie „wizyta” Każdy węzeł w drzewie.visit
Algorytm TreeNode zapewni, że każdy TreeNode zostanie użyty jako argument warrivedAt
metodzie gościa .źródło
Jednym ze sposobów, aby na to spojrzeć, jest to, że wzorzec odwiedzających pozwala klientom dodawać dodatkowe metody do wszystkich klas w określonej hierarchii klas.
Jest to przydatne, gdy masz dość stabilną hierarchię klas, ale zmieniają się wymagania dotyczące tego, co należy zrobić z tą hierarchią.
Klasyczny przykład dotyczy kompilatorów i tym podobnych. Streszczenie drzewa składni (AST) może dokładnie zdefiniować strukturę języka programowania, ale operacje, które możesz chcieć wykonywać na AST, zmienią się wraz z postępem projektu: generatory kodów, ładne drukarki, debuggery, analiza metryk złożoności.
Bez wzorca gościa za każdym razem, gdy programista chciał dodać nową funkcję, musiałby dodać tę metodę do każdej funkcji w klasie podstawowej. Jest to szczególnie trudne, gdy klasy podstawowe pojawiają się w osobnej bibliotece lub są tworzone przez oddzielny zespół.
(Słyszałem, że argumentował, że wzorzec gościa jest w konflikcie z dobrymi praktykami OO, ponieważ odsuwa operacje danych od danych. Wzorzec gościa jest przydatny w sytuacji, w której zawodzą normalne praktyki OO).
źródło
Istnieją co najmniej trzy bardzo dobre powody, aby używać Wzorca Odwiedzającego:
Ograniczaj rozprzestrzenianie się kodu, który tylko nieznacznie różni się, gdy zmieniają się struktury danych.
Zastosuj to samo obliczenie do kilku struktur danych, bez zmiany kodu, który implementuje obliczenia.
Dodaj informacje do starszych bibliotek bez zmiany starszego kodu.
Proszę spojrzeć na artykuł, który o tym napisałem .
źródło
Jak już zauważył Konrad Rudolph, nadaje się do przypadków, w których potrzebujemy podwójnej wysyłki
Oto przykład pokazujący sytuację, w której potrzebujemy podwójnej wysyłki i jak gość pomaga nam w tym.
Przykład:
Powiedzmy, że mam 3 rodzaje urządzeń mobilnych - iPhone, Android, Windows Mobile.
Wszystkie te trzy urządzenia mają zainstalowane radio Bluetooth.
Załóżmy, że radio z niebieskim zębem może pochodzić od 2 oddzielnych producentów OEM - Intel i Broadcom.
Aby uczynić przykład odpowiednim dla naszej dyskusji, załóżmy również, że interfejsy API udostępniane przez radio Intela różnią się od tych udostępnianych przez radio Broadcom.
Tak wyglądają moje zajęcia -
Teraz chciałbym wprowadzić operację - Włączanie Bluetooth na urządzeniu mobilnym.
Podpis funkcji powinien polubić coś takiego -
Tak więc w zależności od odpowiedniego rodzaju urządzenia i w zależności od odpowiedniego rodzaju radia Bluetooth można go włączyć, wywołując odpowiednie kroki lub algorytm .
Zasadniczo staje się matrycą 3 x 2, gdzie staram się wektorować właściwą operację w zależności od odpowiedniego rodzaju obiektów.
Zachowanie polimorficzne w zależności od rodzaju obu argumentów.
Teraz do tego problemu można zastosować wzór użytkownika. Inspiracja pochodzi ze strony Wikipedii: „Zasadniczo odwiedzający pozwala dodawać nowe funkcje wirtualne do rodziny klas bez modyfikowania samych klas; zamiast tego tworzy się klasę gości, która implementuje wszystkie odpowiednie specjalizacje funkcji wirtualnej. Gość przyjmuje referencję instancji jako dane wejściowe i realizuje cel poprzez podwójną wysyłkę. ”
Podwójna wysyłka jest tutaj konieczna ze względu na matrycę 3x2
Oto jak będzie wyglądać konfiguracja -
Napisałem przykład, aby odpowiedzieć na inne pytanie, kod i jego wyjaśnienie jest tutaj wymienione .
źródło
Łatwiej mi było znaleźć następujące linki:
W http://www.remondo.net/visitor-pattern-example-csharp/ znalazłem przykład, który pokazuje próbny przykład, który pokazuje, jakie są zalety wzorca odwiedzin. Tutaj masz różne klasy kontenerów dla
Pill
:Jak widać powyżej,
BilsterPack
zawierasz pary Pigułek, więc musisz pomnożyć liczbę par przez 2. Możesz także zauważyć, żeBottle
użycieunit
jest innym typem danych i trzeba je rzucić.Tak więc w głównej metodzie możesz obliczyć liczbę pigułek za pomocą następującego kodu:
Zauważ, że powyższy kod narusza
Single Responsibility Principle
. Oznacza to, że musisz zmienić główny kod metody, jeśli dodasz nowy typ kontenera. Wydłużanie czasu przełączania jest również złą praktyką.Więc wprowadzając następujący kod:
Przeniosłeś odpowiedzialność za zliczanie liczby
Pill
s do klasy o nazwiePillCountVisitor
(I usunęliśmy instrukcję case switch). Oznacza to, że ilekroć musisz dodać nowy typ pojemnika na pigułki, powinieneś zmienić tylkoPillCountVisitor
klasę. Zauważ też, żeIVisitor
interfejs jest ogólny do użycia w innych scenariuszach.Dodając metodę Accept do klasy pojemnika na pigułki:
pozwalamy odwiedzającym odwiedzać klasy pojemników na pigułki.
Na koniec obliczamy liczbę tabletek za pomocą następującego kodu:
To znaczy: każdy pojemnik na pigułki pozwala
PillCountVisitor
odwiedzającemu zobaczyć, jak liczą się jego pigułki. On wie, jak liczyć pigułki.U
visitor.Count
ma wartość pigułek.W http://butunclebob.com/ArticleS.UncleBob.IuseVisitor widzisz prawdziwy scenariusz, w którym nie możesz użyć polimorfizmu (odpowiedzi), aby przestrzegać zasady pojedynczej odpowiedzialności. W rzeczywistości w:
reportQtdHoursAndPay
metoda służy do informowania i reprezentacji, a to naruszać jednolitego odpowiedzialność zasady. Dlatego lepiej jest użyć wzorca odwiedzającego, aby rozwiązać problem.źródło
Podwójna wysyłka jest tylko jednym z powodów, dla których warto skorzystać z tego wzoru .
Należy jednak pamiętać, że jest to jeden sposób na wdrożenie podwójnej lub większej liczby wysyłek w językach korzystających z paradygmatu pojedynczej wysyłki.
Oto powody, dla których warto użyć wzorca:
1) Chcemy definiować nowe operacje bez zmiany modelu za każdym razem, ponieważ model nie zmienia się często operacje zmieniania często.
2) Nie chcemy łączyć modelu z zachowaniem, ponieważ chcemy mieć model wielokrotnego użytku w wielu aplikacjach lub chcemy mieć rozszerzalny model, który pozwala klasom klientów definiować ich zachowania za pomocą własnych klas.
3) Mamy wspólne operacje, które zależą od konkretnego typu modelu, ale nie chcemy implementować logiki w każdej podklasie, ponieważ rozłożyłoby to logikę na wiele klas, a więc w wielu miejscach .
4) Używamy projektu modelu domeny, a klasy modeli o tej samej hierarchii wykonują zbyt wiele różnych rzeczy, które można by zebrać gdzie indziej .
5) Potrzebujemy podwójnej wysyłki .
Mamy zmienne zadeklarowane z typami interfejsów i chcemy móc je przetwarzać zgodnie z typem środowiska wykonawczego… oczywiście bez użycia
if (myObj instanceof Foo) {}
żadnej sztuczki.Pomysł polega na przykład na przekazaniu tych zmiennych do metod, które deklarują konkretny typ interfejsu jako parametr do zastosowania określonego przetwarzania. Ten sposób działania nie jest możliwy po wyjęciu z pudełka, ponieważ języki zależą od pojedynczej wysyłki, ponieważ wybrana funkcja wywoływana w czasie wykonywania zależy tylko od typu środowiska uruchomieniowego odbiornika.
Zauważ, że w Javie metoda (podpis) do wywołania jest wybierana w czasie kompilacji i zależy od zadeklarowanego typu parametrów, a nie ich typu środowiska wykonawczego.
Ostatni punkt, który jest powodem do korzystania z gościa, jest również konsekwencją, ponieważ podczas implementowania gościa (oczywiście w przypadku języków, które nie obsługują wielokrotnej wysyłki), koniecznie trzeba wprowadzić implementację podwójnej wysyłki.
Pamiętaj, że przejście elementów (iteracja) w celu zastosowania użytkownika na każdym z nich nie jest powodem do użycia wzoru.
Używasz wzorca, ponieważ dzielisz model i przetwarzanie.
Korzystając ze wzoru, zyskujesz dodatkowo na zdolności iteratora.
Ta umiejętność jest bardzo potężna i wykracza poza iterację na typie zwykłym z określoną metodą, podobnie jak
accept()
metoda ogólna.Jest to specjalny przypadek użycia. Odłożę to na bok.
Przykład w Javie
Zilustruję wartość dodaną wzoru na przykładzie szachów, w którym chcielibyśmy zdefiniować przetwarzanie, gdy gracz zażąda ruchu elementu.
Bez użycia wzorca odwiedzającego moglibyśmy zdefiniować zachowania związane z przemieszczaniem elementów bezpośrednio w podklasach elementów.
Możemy mieć na przykład
Piece
interfejs taki jak:Każda podklasa Piece zaimplementuje to, na przykład:
To samo dotyczy wszystkich podklas Piece.
Oto klasa diagramów ilustrująca ten projekt:
Takie podejście ma trzy ważne wady:
- zachowania takie jak
performMove()
lubcomputeIfKingCheck()
najprawdopodobniej wykorzystają wspólną logikę.Na przykład, bez względu na konkretny
Piece
,performMove()
ostatecznie ustawi bieżący element w określonym miejscu i potencjalnie zabierze element przeciwnika.Podział pokrewnych zachowań na wiele klas zamiast gromadzenia ich, w pewien sposób pokonuje pojedynczy wzorzec odpowiedzialności. Utrudniając ich konserwację.
- przetwarzanie, które
checkMoveValidity()
nie powinno być czymś, coPiece
podklasy mogą zobaczyć lub zmienić.Jest to czek wykraczający poza działania człowieka lub komputera. Ta kontrola jest wykonywana przy każdej akcji żądanej przez gracza, aby upewnić się, że żądany ruch pionka jest prawidłowy.
Więc nawet nie chcemy tego podawać w
Piece
interfejsie.- W grach szachowych stanowiących wyzwanie dla twórców botów, ogólnie aplikacja zapewnia standardowy interfejs API (
Piece
interfejsy, podklasy, planszowe, wspólne zachowania itp.) I pozwala programistom wzbogacić swoją strategię botów.Aby to zrobić, musimy zaproponować model, w którym dane i zachowania nie są ściśle powiązane z
Piece
implementacjami.Przejdźmy więc do wzorca odwiedzin!
Mamy dwa rodzaje struktur:
- wzorcowe klasy, które akceptują zwiedzanie (sztuki)
- odwiedzający je odwiedzający (przeprowadzki)
Oto schemat klas ilustrujący wzorzec:
W górnej części mamy gości, aw dolnej mamy klasy modeli.
Oto
PieceMovingVisitor
interfejs (zachowanie określone dla każdego rodzajuPiece
):Kawałek jest teraz zdefiniowany:
Jego kluczową metodą jest:
Zapewnia pierwszą wysyłkę: wywołanie na podstawie
Piece
odbiorcy.W czasie kompilacji metoda jest powiązana z
accept()
metodą interfejsu Piece, aw czasie wykonywania metoda ograniczona zostanie wywołana wPiece
klasie środowiska wykonawczego .I to
accept()
implementacja metody wykona drugą wysyłkę.Rzeczywiście, każda
Piece
podklasa, która chce być odwiedzana przezPieceMovingVisitor
obiekt, wywołujePieceMovingVisitor.visit()
metodę, przekazując jako sam argument.W ten sposób kompilator ogranicza, jak tylko czas kompilacji, typ zadeklarowanego parametru o typie konkretnym.
Jest druga wysyłka.
Oto
Bishop
podklasa, która ilustruje to:A tutaj przykład użycia:
Wady odwiedzających
Wzorzec gościa jest bardzo silnym wzorcem, ale ma również pewne ważne ograniczenia, które należy rozważyć przed użyciem.
1) Ryzyko zmniejszenia / zerwania kapsułkowania
W niektórych rodzajach operacji wzorzec użytkownika może zmniejszyć lub przerwać enkapsulację obiektów domeny.
Na przykład, ponieważ
MovePerformingVisitor
klasa musi ustawić współrzędne rzeczywistego elementu,Piece
interfejs musi zapewnić sposób, aby to zrobić:Odpowiedzialność za
Piece
zmiany współrzędnych jest teraz otwarta dla innych klas niżPiece
podklasy.Przeniesienie przetwarzania wykonywanego przez gościa w
Piece
podklasach również nie jest opcją.To rzeczywiście stworzy inny problem, ponieważ
Piece.accept()
akceptuje każdą implementację odwiedzającego. Nie wie, co robi użytkownik, więc nie ma pojęcia o tym, czy i jak zmienić stan Piece.Sposobem na identyfikację gościa byłoby wykonanie przetwarzania końcowego
Piece.accept()
zgodnie z implementacją gościa. Byłoby to bardzo zły pomysł, gdyż powodowałoby duże sprzężenie pomiędzy implementacjami gościem i podklasy kawałku i oprócz to prawdopodobnie wymagać, aby użyć jako sztuczkęgetClass()
,instanceof
lub dowolny znacznik określający realizację odwiedzających.2) Wymóg zmiany modelu
W przeciwieństwie do niektórych innych wzorców behawioralnych, takich jak
Decorator
na przykład wzorzec odwiedzających jest nachalny.Rzeczywiście musimy zmodyfikować początkową klasę odbiornika, aby zapewnić
accept()
metodę akceptacji odwiedzin.Nie mieliśmy żadnych problemów
Piece
i podklas, ponieważ są to nasze klasy .W przypadku klas wbudowanych lub zajęć dla osób trzecich sprawy nie są takie proste.
Musimy je zawinąć lub odziedziczyć (jeśli możemy), aby dodać
accept()
metodę.3) Kierunki
Wzór tworzy wielokrotność pośrednich.
Podwójna wysyłka oznacza dwie inwokacje zamiast jednej:
I możemy mieć dodatkowe pośrednie, gdy gość zmienia stan odwiedzanego obiektu.
Może to wyglądać jak cykl:
źródło
Cay Horstmann ma świetny przykład zastosowania Odwiedzającego w swojej książce projektowej i wzorach OO . Podsumowuje problem:
Nie jest to łatwe, ponieważ operacje są dodawane w ramach samych klas struktur. Na przykład wyobraź sobie, że masz system plików:
Oto niektóre operacje (funkcjonalności), które moglibyśmy chcieć wdrożyć za pomocą tej struktury:
Możesz dodać funkcje do każdej klasy w systemie plików, aby zaimplementować operacje (a ludzie robili to w przeszłości, ponieważ to bardzo oczywiste, jak to zrobić). Problem polega na tym, że za każdym razem, gdy dodajesz nową funkcjonalność (wiersz „itd.” Powyżej), może być konieczne dodawanie coraz większej liczby metod do klas struktur. W pewnym momencie, po pewnej liczbie operacji dodanych do oprogramowania, metody w tych klasach nie mają już sensu pod względem spójności funkcjonalnej klas. Na przykład masz
FileNode
metodę, która macalculateFileColorForFunctionABC()
zaimplementować najnowszą funkcjonalność wizualizacji w systemie plików.Wzorzec gościa (podobnie jak wiele wzorców projektowych) narodził się z bólu i cierpienia programistów, którzy wiedzieli, że istnieje lepszy sposób na zmianę kodu bez konieczności wprowadzania wielu zmian wszędzie, a także z poszanowaniem dobrych zasad projektowania (wysoka spójność, niskie sprzężenie) ). Moim zdaniem, trudno jest zrozumieć przydatność wielu wzorów, dopóki nie poczujesz tego bólu. Wyjaśnienie bólu (tak jak próbujemy to zrobić powyżej za pomocą dodanych funkcji „itp.”) Zajmuje miejsce w wyjaśnieniu i jest rozproszeniem. Z tego powodu zrozumienie wzorców jest trudne.
Visitor pozwala nam oddzielić funkcje w strukturze danych (np.
FileSystemNodes
) Od samych struktur danych. Wzorzec pozwala projektowi zachować spójność - klasy struktur danych są prostsze (mają mniej metod), a także funkcjonalności są zawarte wVisitor
implementacjach. Odbywa się to poprzez podwójne wywoływanie (co jest skomplikowaną częścią wzorca): przy użyciuaccept()
metod w klasach struktur ivisitX()
metod w klasach Visitor (funkcjonalność):Ta struktura pozwala nam dodawać nowe funkcjonalności, które działają na strukturze jako konkretne osoby odwiedzające (bez zmiany klas struktur).
Na przykład: a,
PrintNameVisitor
który implementuje funkcję wyświetlania katalogu, a a,PrintSizeVisitor
który implementuje wersję o rozmiarze. Możemy sobie wyobrazić, że pewnego dnia mamy „ExportXMLVisitor”, który generuje dane w formacie XML, lub innego użytkownika, który generuje je w JSON, itp. Moglibyśmy nawet mieć gościa, który wyświetla moje drzewo katalogów za pomocą języka graficznego takiego jak DOT , do wizualizacji z innym programem.Na koniec: złożoność Visora z podwójną wysyłką oznacza, że trudniej jest go zrozumieć, zakodować i debugować. Krótko mówiąc, ma wysoki współczynnik maniaka i idzie w parze z zasadą KISS. W ankiecie przeprowadzonej przez badaczy, Visitor wykazał kontrowersyjny wzorzec (nie było zgody co do jego przydatności). Niektóre eksperymenty wykazały nawet, że nie ułatwia to utrzymania kodu.
źródło
Moim zdaniem nakład pracy związany z dodaniem nowej operacji jest mniej więcej taki sam przy użyciu
Visitor Pattern
lub bezpośredniej modyfikacji struktury każdego elementu. Ponadto, jeśli miałbym dodać nową klasę elementów, powiedzmyCow
, wpłynie to na interfejs Operacji, co spowoduje propagację do wszystkich istniejących klas elementów, co wymaga ponownej kompilacji wszystkich klas elementów. Więc o co chodzi?źródło
rootElement.visit (node) -> node.collapse()
. Z gościem, każdy węzeł implementuje przechodzenie wykresu dla wszystkich swoich dzieci, więc gotowe.levelsRemaining
liczniku jako parametr. Zmniejsz to przed wywołaniem następnego poziomu dzieci. Wewnątrz twojego gościaif(levelsRemaining == 0) return
.Wzorzec gościa jako ta sama podziemna implementacja programowania obiektów Aspect ..
Na przykład, jeśli zdefiniujesz nową operację bez zmiany klas elementów, na których ona działa
źródło
Krótki opis wzoru odwiedzającego. Wszystkie klasy wymagające modyfikacji muszą implementować metodę „accept”. Klienci nazywają tę metodę akceptacji, aby wykonać nowe działanie na tej rodzinie klas, rozszerzając w ten sposób swoją funkcjonalność. Klienci mogą skorzystać z tej jednej metody akceptacji, aby wykonać szeroki zakres nowych akcji, przekazując inną klasę odwiedzających dla każdej konkretnej akcji. Klasa odwiedzających zawiera wiele przesłoniętych metod odwiedzin definiujących, jak osiągnąć to samo działanie dla każdej klasy w rodzinie. Te metody odwiedzin otrzymują instancję, na której można pracować.
Kiedy możesz rozważyć jego użycie
źródło
Nie zrozumiałem tego wzoru, dopóki nie natknąłem się na artykuł wuja boba i nie przeczytałem komentarzy. Rozważ następujący kod:
Choć może wyglądać dobrze, ponieważ potwierdza Pojedynczą Odpowiedzialność , narusza zasadę Otwartej / Zamkniętej . Za każdym razem, gdy masz nowy typ pracownika, będziesz musiał go dodać, jeśli sprawdzasz typ. A jeśli tego nie zrobisz, nigdy nie dowiesz się tego podczas kompilacji.
Dzięki wzorowi odwiedzających możesz uczynić swój kod czystszym, ponieważ nie narusza zasady otwartej / zamkniętej i nie narusza Pojedynczej odpowiedzialności. A jeśli zapomnisz zaimplementować wizytę, nie zostanie ona skompilowana:
Magia polega na tym, że chociaż
v.Visit(this)
wygląda tak samo, w rzeczywistości jest inna, ponieważ wywołuje różne przeciążenia odwiedzających.źródło
Na podstawie doskonałej odpowiedzi @Federico A. Ramponi.
Wyobraź sobie, że masz tę hierarchię:
Co się stanie, jeśli musisz tutaj dodać metodę „Spacer”? Będzie to bolesne dla całego projektu.
Jednocześnie dodanie metody „Spacer” generuje nowe pytania. Co z „jedzeniem” lub „snem”? Czy naprawdę musimy dodawać nową metodę do hierarchii Zwierząt dla każdej nowej akcji lub operacji, którą chcemy dodać? To brzydkie i najważniejsze, nigdy nie będziemy w stanie zamknąć interfejsu Zwierząt. Dzięki wzorowi użytkowników możemy dodawać nową metodę do hierarchii bez modyfikowania hierarchii!
Więc po prostu sprawdź i uruchom ten przykład w C #:
źródło
Dog
jak i dlaCat
. Mogłeś zrobić je w klasie podstawowej, aby były dziedziczone lub wybrać odpowiedni przykład.Gość
Struktura odwiedzających:
Użyj wzorca użytkownika, jeśli:
Chociaż Visitor wzór zapewnia elastyczność, aby dodać nową operację bez zmiany istniejącego kodu w obiekcie, elastyczność ta ma pochodzić z wadą.
Jeśli dodano nowy obiekt możliwy do zwiedzania, wymaga on zmiany kodu w klasach Visitor i ConcreteVisitor . Istnieje sposób obejścia tego problemu: Użyj refleksji, która wpłynie na wydajność.
Fragment kodu:
Wyjaśnienie:
Visitable
(Element
) jest interfejsem i tę metodę interfejsu należy dodać do zestawu klas.Visitor
to interfejs zawierający metody wykonywania operacji naVisitable
elementach.GameVisitor
jest klasą, która implementujeVisitor
interface (ConcreteVisitor
).Visitable
element akceptujeVisitor
i wywołuje odpowiednią metodęVisitor
interfejsu.Game
jakElement
i konkretne gry, takie jakChess,Checkers and Ludo
jakConcreteElements
.W powyższym przykładzie
Chess, Checkers and Ludo
są trzy różne gry (iVisitable
klasy). Pewnego pięknego dnia napotkałem scenariusz rejestrowania statystyk każdej gry. Zatem bez modyfikowania poszczególnych klas w celu zaimplementowania funkcji statystycznych możesz scentralizować tę odpowiedzialność wGameVisitor
klasie, co załatwi sprawę bez modyfikowania struktury każdej gry.wynik:
Odnosić się do
artykuł oodesign
artykuł o zaopatrzeniu
po więcej szczegółów
Dekorator
Powiązane posty:
Wzór dekoratora dla IO
Kiedy stosować wzór dekoratora?
źródło
Bardzo podoba mi się opis i przykład z http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .
źródło
Chociaż rozumiem, jak i kiedy, nigdy nie zrozumiałem, dlaczego. Jeśli pomaga to każdemu, kto ma doświadczenie w języku takim jak C ++, chcesz przeczytać to bardzo uważnie.
Dla leniwych używamy wzorca odwiedzin, ponieważ „podczas gdy funkcje wirtualne są wywoływane dynamicznie w C ++, przeciążanie funkcji odbywa się statycznie” .
Lub, inaczej mówiąc, aby upewnić się, że CollideWith (ApolloSpacecraft &) jest wywoływany, gdy przechodzisz w odniesieniu do statku kosmicznego, który jest faktycznie związany z obiektem ApolloSpacecraft.
źródło
Dzięki za niesamowite wyjaśnienie @Federico A. Ramponi , właśnie to zrobiłem w wersji java . Mam nadzieję, że to może być pomocne.
Również, jak zauważył @Konrad Rudolph , tak naprawdę jest to podwójna wysyłka z wykorzystaniem dwóch konkretnych instancji razem w celu określenia metod działania.
Tak więc właściwie nie ma potrzeby tworzenia wspólnego interfejsu dla executora operacji, o ile mamy poprawnie zdefiniowany interfejs operacji .
Jak można się spodziewać, wspólny interfejs zapewni nam większą przejrzystość, choć tak naprawdę nie jest to niezbędna część tego wzorca.
źródło
twoje pytanie brzmi: kiedy wiedzieć:
nie najpierw koduję według wzorca odwiedzającego. koduję standard i czekam na konieczność wystąpienia, a następnie refaktoryzuję. powiedzmy, że masz wiele systemów płatności, które zainstalowałeś jednocześnie. W kasie możesz mieć wiele warunków if (lub instanceOf), na przykład:
teraz wyobraź sobie, że mam 10 metod płatności, robi się trochę brzydko. Więc kiedy zobaczysz, że taki wzorzec występujący w odwiedzinach przydaje się, aby oddzielić to wszystko, a potem w końcu wywołujesz coś takiego:
Możesz zobaczyć, jak to zaimplementować na podstawie wielu przykładów tutaj, po prostu pokazuję ci przypadek użycia.
źródło