Wzór gościa
W programowaniu obiektowym i inżynierii oprogramowania wzorzec projektowy gościa jest sposobem na oddzielenie algorytmu od struktury obiektowej , na której działa. Praktycznym skutkiem tego rozdzielenia jest możliwość dodawania nowych operacji do istniejących struktur obiektów bez modyfikowania struktur. Jest to jeden ze sposobów przestrzegania zasady otwarte/zamknięte .
Zasadniczo odwiedzający umożliwia dodawanie nowych funkcji wirtualnych do rodziny klas bez modyfikowania klas. Zamiast tego tworzona jest klasa gościa, która implementuje wszystkie odpowiednie specjalizacje funkcji wirtualnej. Odwiedzający przyjmuje jako dane wejściowe odwołanie do instancji i realizuje cel poprzez podwójną wysyłkę .
Języki programowania z typami sum i dopasowywaniem wzorców eliminują wiele zalet wzorca odwiedzającego, ponieważ klasa odwiedzającego może zarówno łatwo rozgałęziać się na typie obiektu, jak i generować błąd kompilatora, jeśli zostanie zdefiniowany nowy typ obiektu, co robi odwiedzający jeszcze nie obsługiwać.
Przegląd
Wzorzec projektowy Visitor jest jednym z dwudziestu trzech dobrze znanych wzorców projektowych Gang of Four , które opisują, jak rozwiązywać powtarzające się problemy projektowe w celu zaprojektowania elastycznego i wielokrotnego użytku oprogramowania zorientowanego obiektowo, czyli obiektów łatwiejszych do wdrożenia, zmiany, przetestowania i ponownie użyć.
Jakie problemy może rozwiązać wzorzec projektowy Visitor?
- Powinno być możliwe zdefiniowanie nowej operacji dla (niektórych) klas struktury obiektowej bez zmiany klas.
Kiedy często potrzebne są nowe operacje, a struktura obiektu składa się z wielu niepowiązanych ze sobą klas, dodawanie nowych podklas za każdym razem, gdy wymagana jest nowa operacja, jest nieelastyczne, ponieważ „[..] rozłożenie wszystkich tych operacji na różne klasy węzłów prowadzi do systemu, który jest trudny zrozumieć, utrzymać i zmienić”.
Jakie rozwiązanie opisuje wzorzec projektowy Odwiedzający?
- Zdefiniuj oddzielny obiekt (gość), który implementuje operację do wykonania na elementach struktury obiektu.
- Klienci przechodzą przez strukturę obiektu i wywołują operację wysyłania accept (gość) na elemencie — który „wysyła” (deleguje) żądanie do „zaakceptowanego obiektu gościa”. Następnie obiekt odwiedzający wykonuje operację na elemencie („odwiedza element”).
Dzięki temu możliwe jest tworzenie nowych operacji niezależnie od klas struktury obiektów poprzez dodawanie nowych obiektów odwiedzających.
Zobacz także diagram klas i sekwencji UML poniżej.
Definicja
Gang Czterech definiuje Gościa jako:
Reprezentuje operację, która ma zostać wykonana na elementach struktury obiektu. Visitor umożliwia zdefiniowanie nowej operacji bez zmiany klas elementów, na których operuje.
Charakter Visitora sprawia, że jest to idealny wzorzec do podłączania do publicznych interfejsów API, umożliwiając w ten sposób jego klientom wykonywanie operacji na klasie przy użyciu klasy „odwiedzającej” bez konieczności modyfikowania źródła.
Zalety
Przenoszenie operacji do klas odwiedzających jest korzystne, gdy
- wymaganych jest wiele niepowiązanych ze sobą operacji na strukturze obiektu,
- klasy tworzące strukturę obiektu są znane i nie oczekuje się ich zmiany,
- trzeba często dodawać nowe operacje,
- algorytm obejmuje kilka klas struktury obiektu, ale pożądane jest zarządzanie nim w jednym miejscu,
- algorytm musi działać w kilku niezależnych hierarchiach klas.
Wadą tego wzorca jest jednak to, że utrudnia on rozszerzenie hierarchii klas, ponieważ nowe zajęcia zwykle wymagają dodania nowej metody wizyty do każdego odwiedzającego.
Aplikacja
Rozważ projekt systemu wspomaganego komputerowo projektowania 2D (CAD). Zasadniczo istnieje kilka typów reprezentujących podstawowe kształty geometryczne, takie jak koła, linie i łuki. Elementy są uporządkowane w warstwach, a na szczycie hierarchii typów znajduje się rysunek, który jest po prostu listą warstw plus kilka dodanych właściwości.
Podstawową operacją na tej hierarchii typów jest zapisanie rysunku do natywnego formatu pliku systemowego. Na pierwszy rzut oka dodawanie lokalnych metod składowania do wszystkich typów w hierarchii może wydawać się akceptowalne. Ale przydatna jest również możliwość zapisywania rysunków w innych formatach plików. Dodawanie coraz większej liczby metod zapisywania w wielu różnych formatach plików wkrótce zaśmieca stosunkowo czystą oryginalną geometryczną strukturę danych.
Naiwnym sposobem rozwiązania tego problemu byłoby utrzymanie oddzielnych funkcji dla każdego formatu pliku. Taka funkcja zapisywania pobierałaby rysunek jako dane wejściowe, przeglądała go i kodowała do określonego formatu pliku. Ponieważ odbywa się to dla każdego dodanego innego formatu, kumuluje się duplikacja między funkcjami. Na przykład zapisanie kształtu koła w formacie rastrowym wymaga bardzo podobnego kodu, bez względu na to, jaka forma rastra jest używana, i różni się od innych prymitywnych kształtów. Podobnie jest w przypadku innych prymitywnych kształtów, takich jak linie i wielokąty. W ten sposób kod staje się dużą zewnętrzną pętlą przechodzącą przez obiekty, z dużym drzewem decyzyjnym wewnątrz pętli sprawdzającym typ obiektu. Innym problemem związanym z tym podejściem jest to, że bardzo łatwo jest przeoczyć kształt w jednym lub kilku zapisach lub wprowadza się nowy prymitywny kształt, ale procedura zapisywania jest implementowana tylko dla jednego typu pliku, a nie dla innych, co prowadzi do rozszerzenia kodu i konserwacji problemy. Wraz ze wzrostem liczby wersji tego samego pliku utrzymanie go staje się coraz bardziej skomplikowane.
Zamiast tego można zastosować wzorzec odwiedzającego. Koduje operację logiczną na całej hierarchii w jednej klasie zawierającej jedną metodę na typ. W przykładzie CAD każda funkcja zapisu zostałaby zaimplementowana jako oddzielna podklasa Odwiedzający. To usunęłoby wszelkie powielanie kontroli typu i etapów przechodzenia. Spowodowałoby to również, że kompilator narzekałby na pominięcie kształtu.
Pętle iteracyjne
Wzorzec odwiedzającego może być używany do iteracji po strukturach danych podobnych do kontenera , podobnie jak wzorzec Iterator , ale z ograniczoną funkcjonalnością. Na przykład iteracja struktury katalogów może być realizowana przez klasę funkcji zamiast bardziej konwencjonalnego wzorca pętli . Umożliwiłoby to uzyskanie różnych przydatnych informacji z zawartości katalogów poprzez wdrożenie funkcji odwiedzającego dla każdego elementu podczas ponownego użycia kod iteracji. Jest szeroko stosowany w systemach Smalltalk i można go również znaleźć w C++. Wadą tego podejścia jest jednak to, że nie można łatwo wyrwać się z pętli ani wykonać iteracji jednocześnie (równolegle, tj. przechodzenie przez dwa kontenery w tym samym czasie przez pojedynczą zmienną i )
. To ostatnie wymagałoby napisania dodatkowej funkcjonalności dla odwiedzającego w celu obsługi tych funkcji.
Struktura
Diagram klas i sekwencji UML
Na powyższym diagramie klas UML klasa ElementA
nie implementuje bezpośrednio nowej operacji. Zamiast tego ElementA
implementuje operację wysyłania accept(visitor)
, która „wysyła” (deleguje) żądanie do „zaakceptowanego obiektu odwiedzającego” ( visitor.visitElementA(this)
). Klasa Visitor1
implementuje operację ( visitElementA(e:ElementA)
). Następnie ElementB
implementuje accept(visitor)
, wysyłając do gość.visitElementB(to)
. Klasa Visitor1
implementuje operację ( visitElementB(e:ElementB)
).
Diagram sekwencji UML pokazuje interakcje w czasie wykonywania: Obiekt Client
przechodzi przez elementy struktury obiektu ( ElementA,ElementB
) i wywołuje accept(visitor)
na każdym elemencie. Najpierw Klient
wywołuje accept(visitor)
na ElementA
, który wywołuje visitElementA(this)
na zaakceptowanym obiekcie gościa
. Sam element ( this
) jest przekazywany odwiedzającemu, aby
mógł „odwiedzić” ElementA
( operacja wywołania A()
). Następnie Klient
wywołuje funkcję accept(visitor)
na ElementB
, która wywołuje visitElementB(this)
na gościu
, który „odwiedzi” ElementB
(wywołuje operacjęB()
).
Diagram klas
Detale
Wzorzec odwiedzającego wymaga języka programowania , który obsługuje pojedynczą wysyłkę , podobnie jak popularne języki obiektowe (takie jak C++ , Java , Smalltalk , Objective-C , Swift , JavaScript , Python i C# ). W tych warunkach rozważmy dwa obiekty, każdy z jakiegoś typu klasy; jeden jest określany jako element , a drugi jako gość .
Odwiedzający deklaruje metodę wizyty
, która przyjmuje element jako argument dla każdej klasy elementu . Konkretni odwiedzający wywodzą się z klasy odwiedzającego i implementują te metody odwiedzania
, z których każda implementuje część algorytmu działającego na strukturze obiektu. Stan algorytmu jest utrzymywany lokalnie przez konkretną klasę odwiedzających.
Element deklaruje metodę accept , aby zaakceptować gościa
, przyjmując gościa jako argument. Elementy konkretne , wywodzące się z klasy elementów, implementują metodę accept
. W najprostszej formie jest to nic innego jak wywołanie odwiedzin odwiedzającego
. Elementy złożone , które przechowują listę obiektów podrzędnych, zwykle iterują po nich, wywołując metodę akceptacji każdego elementu podrzędnego.
Klient tworzy strukturę obiektu, bezpośrednio lub pośrednio, i tworzy instancje konkretnych gości. Gdy ma zostać wykonana operacja, która jest realizowana przy użyciu wzorca Visitor, wywołuje accept
elementu(ów) najwyższego poziomu.
Gdy w programie wywoływana jest metoda accept
, jej implementacja jest wybierana na podstawie zarówno dynamicznego typu elementu, jak i statycznego typu odwiedzającego. Kiedy wywoływana jest powiązana wizyty
, jej implementacja jest wybierana na podstawie zarówno dynamicznego typu odwiedzającego, jak i statycznego typu elementu, co jest znane z implementacji metody accept, która
jest taka sama jak typ dynamiczny elementu element. (Jako bonus, jeśli odwiedzający nie może obsłużyć argumentu typu danego elementu, kompilator wychwyci błąd.)
Zatem implementacja metody wizyty
jest wybierana na podstawie zarówno dynamicznego typu elementu, jak i dynamicznego typu odwiedzającego. To skutecznie implementuje podwójną wysyłkę . W przypadku języków, których systemy obiektowe obsługują wielokrotne wysyłanie, a nie tylko pojedyncze wysyłanie, takie jak Common Lisp lub C# za pośrednictwem środowiska wykonawczego języka dynamicznego (DLR) implementacja wzorca odwiedzającego jest znacznie uproszczona (znana również jako Dynamiczny Odwiedzający), umożliwiając użycie prostego przeciążania funkcji w celu pokrycia wszystkich odwiedzanych spraw. Dynamiczny gość, o ile operuje wyłącznie na danych publicznych, spełnia zasadę open/closed (ponieważ nie modyfikuje istniejących struktur) oraz zasadę pojedynczej odpowiedzialności (ponieważ implementuje wzorzec Visitor w osobnym komponencie).
W ten sposób można napisać jeden algorytm do przechodzenia przez graf elementów, a podczas tego przechodzenia można wykonywać wiele różnych rodzajów operacji, dostarczając różnego rodzaju odwiedzających do interakcji z elementami w oparciu o dynamiczne typy zarówno elementów, jak i goście.
Przykład C#
Ten przykład deklaruje oddzielną klasę ExpressionPrintingVisitor
, która zajmuje się drukowaniem.
przestrzeń nazw Wikipedia ; klasa publiczna ExpressionPrintingVisitor { public void PrintLiteral ( dosłowny literał ) { Console . WriteLine ( dosłownie . Wartość ); } public void PrintAddition ( Dodawanie dodawanie ) { double leftValue = dodawanie . lewo . Pobierz wartość
(); double rightValue = dodawanie . dobrze . Pobierz wartość (); zmienna suma = dodawanie . Pobierz wartość (); Konsola . WriteLine ( "{0} + {1} = {2}" , leftValue , rightValue , suma ); } } public abstract class Wyrażenie { public abstract void Akceptuj (
ExpressionPrintingVisitor v ); publiczne abstrakcyjne podwójne GetValue (); } public class Literał : Wyrażenie { public double Value { get ; zestaw ; } public Literał ( podwójna wartość ) { this . wartość = wartość ; } publiczne nadpisanie nieważne Zaakceptuj
( ExpressionPrintingVisitor v ) { v . PrintLiteral ( to ); } publiczne przesłonięcie double GetValue () { return Value ; } } public class Dodatek : Expression { public Expression Left { get ; zestaw ; } wyrażenie publiczne prawo { get
; zestaw ; } public Dodawanie ( Wyrażenie po lewej , Wyrażenie po prawej ) { Lewa = lewa ; prawo = prawo ; } publiczne przesłonięcie void Akceptuj ( ExpressionPrintingVisitor v ) { Left . Zaakceptuj ( v ); dobrze . Zaakceptuj ( v );
w . PrintAddition ( to ); } publiczne przesłonięcie double GetValue () { return Left . GetValue () + prawo . Pobierz wartość (); } } public static class Program { public static void Main ( string [] args ) { // Emuluj 1 + 2 + 3 var e
= new Addition ( new Addition ( new Literal ( 1 ), new Literal ( 2 ) ), new Literal ( 3 ) ); varprintingVisitor = new ExpressionPrintingVisitor ( ) ; e . Zaakceptuj ( printingVisitor ); } }
Przykład Smalltalka
W tym przypadku obowiązkiem obiektu jest wiedzieć, jak wydrukować się w strumieniu. Gość jest tu więc przedmiotem, a nie strumieniem.
„Nie ma składni do tworzenia klas. Klasy są tworzone przez wysyłanie wiadomości do innych klas”. Podklasa WriteStream : #ExpressionPrinter instanceVariableNames: '' classVariableNames: '' package: 'Wikipedia' . ExpressionPrinter >>write: anObject "Deleguje akcję do obiektu. Obiekt nie musi należeć do żadnej specjalnej klasy; wystarczy, że będzie w stanie zrozumieć komunikat #putOn:" anObject putOn: self
. ^ Obiekt . Podklasa obiektu : #Expression instanceVariableNames: '' classVariableNames: '' package: 'Wikipedia' . Podklasa wyrażenia : #Literal instanceVariableNames: 'value' classVariableNames: '' package: 'Wikipedia' . Klasa literału >>z: aValue "Metoda klasy do budowy instancji klasy Literal"
^ self nowa wartość: aValue ; siebie . Dosłowne >>wartość: aValue Wartość „ustawiającego dla wartości” := aValue . Literał >>putOn: aStream „Obiekt literału wie, jak się wydrukować” aStream nextPutAll: wartość asString . Podklasa wyrażenia : #Addition instanceVariableNames: 'left right' classVariableNames: ''
pakiet: „Wikipedia” . Klasa Addition >>left: a right: b "Klasowa metoda budowania instancji klasy Addition" ^ self new left: a ; po prawej: b ; siebie . Dodatek >>left: anExpression "Ustawiający dla lewej" left := anExpression . Dodatek >>prawo: anWyrażenie „Ustawiający w prawo”
prawo := anExpression . Addition >>putOn: aStream "Obiekt Addition wie, jak się wydrukować" aStream nextPut: $( . left putOn: aStream . aStream nextPut: $+ . right putOn: aStream . aStream nextPut: $) . Podklasa obiektu : #Program instanceVariableNames: '' classVariableNames:
Pakiet '' : 'Wikipedia' . Program >> główny | strumień wyrażeń | wyrażenie := Dodanie w lewo: ( Dodanie w lewo: ( Dosłownie z: 1 ) w prawo: ( Dosłownie z: 2 )) w prawo: ( Dosłownie z: 3 ) . strumień :=
ExpressionPrinter na: ( Ciąg nowy: 100 ) . zapis strumienia : wyrażenie . Pokaż transkrypcję : zawartość strumienia . Przepłukanie transkrypcji .
Iść
Go nie obsługuje przeciążania, więc metody wizyty wymagają różnych nazw. Typowy interfejs gościa może być
typ Interfejs gościa { wizytaKoło ( koło Koło ) string wizytaSilnik ( silnik Silnik ) string wizytaCiało ( nadwozie ) string wizytaSamochód ( samochód Samochód ) string } _
Przykład Javy
Poniższy przykład jest w języku Java i pokazuje, jak można wydrukować zawartość drzewa węzłów (w tym przypadku opisujących komponenty samochodu). Zamiast tworzyć drukowania
dla każdej podklasy węzła ( Wheel
, Engine
, Body
i Car
), jedna klasa odwiedzająca ( CarElementPrintVisitor
) wykonuje wymaganą akcję drukowania. Ponieważ różne podklasy węzłów wymagają nieco innych działań do prawidłowego drukowania, CarElementPrintVisitor
wysyła akcje w oparciu o klasę argumentu przekazanego do jego metody wizyty
. CarElementDoVisitor
, która jest analogiczna do operacji zapisywania dla innego formatu pliku, robi to samo.
Diagram
Źródła
importuj java.util.List ; interfejs CarElement { unieważnić akceptację ( odwiedzający CarElementVisitor ); } interfejs CarElementVisitor { nieważna wizyta ( ciało ciała ); nieważna wizyta ( Samochód samochód ); nieważna wizyta ( Silnik silnika ); nieważna wizyta ( Koło ) ; } klasa
Wheel implementuje CarElement { private final String name ; koło publiczne ( ostateczna nazwa ciągu znaków ) { this . imię = imię ; } public String getName () { return nazwa ; } @Override public void zaakceptuj ( odwiedzający CarElementVisitor ) { /*
* accept(CarElementVisitor) w narzędziach Wheel * accept(CarElementVisitor) w CarElement, więc wywołanie * do accept jest powiązane w czasie wykonywania. Można to uznać za *pierwszą* wysyłkę. Jednak decyzja o wywołaniu * visit(Wheel) (w przeciwieństwie do visit(Engine) itp.) może zostać * podjęta w czasie kompilacji, ponieważ „to” jest znane w czasie kompilacji jako koło. Ponadto każda implementacja * CarElementVisitor implementuje wizytę (koło), która jest * kolejną decyzją podejmowaną w czasie wykonywania. To może być
* rozważał *drugą* wysyłkę. */ gość . odwiedzić ( to ); } } class Body implementuje CarElement { @Override public void accept ( CarElementVisitor odwiedzający ) { odwiedzający . odwiedzić ( to ); } } class Engine implementuje CarElement { @Override public
anuluj akceptację ( odwiedzający CarElementVisitor ) { odwiedzający . odwiedzić ( to ); } } class Car implementuje CarElement { private final List < CarElement > elementy ; samochód publiczny () { to . elementy = lista . z ( nowe koło (
"przedni lewy" ), nowe koło ( "przedni prawy" ), nowe koło ( "tylny lewy" ), nowe koło ( "tylny prawy" ), nowe nadwozie (), nowy silnik () ); } @Override public void zaakceptuj ( odwiedzający CarElementVisitor ) { for ( element CarElement :
elementy ) { element . zaakceptować ( gość ); } gość . odwiedzić ( to ); } } class CarElementDoVisitor implementuje CarElementVisitor { @Override public void visit ( Body body ) { System . na zewnątrz println ( "Przesuwanie mojego ciała" ); }
@Override publiczna nieważna wizyta ( Samochód samochodowy ) { System . na zewnątrz println ( "Uruchamianie samochodu" ); } @Override public void visit ( kółko ) { System . _ na zewnątrz println ( "Kopanie mojego " + kółko . getName () + " kółko "
); } @Override public void visit ( silnik silnika ) { System . na zewnątrz println ( "Uruchamiam silnik" ); } } class CarElementPrintVisitor implementuje CarElementVisitor { @Override public void visit ( Body body ) { System . na zewnątrz println (
„Organ wizytujący” ); } @Override public void visit ( Samochód samochód ) { System . na zewnątrz println ( "Odwiedzający samochód" ); } @Override public void visit ( silnik silnika ) { System . na zewnątrz println ( "Odwiedzający silnik" ); } @Zastąp
publiczna nieważna wizyta ( Koło ) { System . _ na zewnątrz println ( "Odwiedzanie" + koło . getName () + "koło" ); } } public class VisitorDemo { public static void main ( final String [] args ) { Samochód samochód =
nowy samochód (); samochód . zaakceptuj ( nowy CarElementPrintVisitor ()); samochód . zaakceptuj ( nowy CarElementDoVisitor ()); } }
Wyjście
Odwiedzanie lewego przedniego koła Odwiedzanie prawego przedniego koła Odwiedzanie lewego tylnego koła Odwiedzanie prawego tylnego koła Odwiedzanie nadwozia Odwiedzanie silnika Odwiedzanie samochodu Kopanie mojego lewego przedniego koła Kopanie mojego prawego przedniego koła Kopanie lewego tylnego koła Kopanie prawego tylnego koła Poruszanie ciałem Uruchamianie silnika Uruchamianie mojego samochód
Zwykły przykład Lispa
Źródła
( defclass auto () (( elements :initarg :elements ))) ( defclass auto-part () (( name :initarg :name :initform "<nienazwana-część-samochodu>" ))) ( defmethod print-object (( p auto-part ) strumień ) ( print-object ( slot-value p'name ) stream ) ) (
defclass wheel ( auto-part ) ()) ( defclass body ( auto-part ) ()) ( defclass engine ( auto-part ) ()) ( defgeneric trawers ( funkcja obiekt inny-obiekt )) ( defmethod trawers ( funkcja ( a auto ) inny obiekt ) ( with-slots (
elementy ) a ( dolist ( e elementy ) ( funkcja funcall e inny obiekt )))) ;; zrób coś wizytacje ;; catch all ( defmethod zrób coś ( obiekt inny-obiekt ) ( format t "nie wiem jak ~s i ~s powinny wchodzić w interakcje~%" obiekt inny-obiekt )) ;; odwiedziny obejmujące koło i liczbę całkowitą ( defmethod
zrób coś (( koło obiektu ) ( inny obiekt liczba całkowita )) ( format t "kopanie koła ~ s ~ s razy ~ %" obiekt inny obiekt )) ;; wizytacja z udziałem koła i symbolu ( defmethod zrób coś (( obiekt koło ) ( symbol innego obiektu )) ( format t „kopanie koła ~s symbolicznie za pomocą symbolu ~s~%” obiekt
inny obiekt )) ( defmethod zrób coś (( obiekt silnik ) ( inny obiekt liczba całkowita )) ( format t "uruchamianie silnika ~s ~s razy~%" obiekt inny obiekt )) ( defmethod zrób coś (( obiekt silnik ) ( symbol innego obiektu )) ( format t "uruchamianie silnika ~s symbolicznie za pomocą symbolu ~s~%"
obiekt inny-obiekt )) ( let (( a ( make-instance 'auto :elements ` ( , ( make-instance 'koło : nazwa „przednie-lewe-koło” ) , ( make-instance 'koło : nazwa „przednie- prawe koło" ) , ( utwórz instancję "koło : nazwa "tylne lewe koło" ) , (
make-instance 'koło :nazwa "tylne-prawe-koło" ) , ( make-instance 'body :name "body" ) , ( make-instance 'engine :name "engine" ))))) ;; trawers do drukowania elementów ;; strumień *standardowe-wyjście* pełni tutaj rolę innego-obiektu ( traverse #' print a *standard-output* ) ( terpri ) ;; drukuj nową linię
;; traverse z dowolnym kontekstem z innego obiektu ( traverse #' do-coś a 42 ) ;; traverse z dowolnym kontekstem z innego obiektu ( trawers #' do-coś a 'abc ))
Wyjście
„przednie lewe koło” „przednie prawe koło” „tylne lewe koło” „tylne prawe koło” „nadwozie” „silnik” kopanie koła „przednie lewe koło” 42 razy kopanie koła „przednie- prawe koło” 42 razy kopanie koła „tylne lewe koło” 42 razy kopanie koła „tylne prawe koło” 42 razy nie wiem jak „ciało” i 42 powinno oddziaływać uruchamianie silnika „silnik” 42 razy kopanie koła „ przednie lewe koło” symbolicznie za pomocą symbolu ABC kopiące koło „przednie prawe koło” symbolicznie za pomocą symbolu ABC kopiące koło „tylne lewe koło” symbolicznie za pomocą symbolu ABC kopiące koło „tylne prawe koło” symbolicznie za pomocą symbolu ABC don Nie wiem, w jaki sposób „nadwozie” i ABC powinny oddziaływać na siebie uruchamiając silnik „silnik” symbolicznie za pomocą symbolu ABC
Notatki
Parametr innego obiektu
jest zbędny w traverse
. Powodem jest to, że możliwe jest użycie anonimowej funkcji, która wywołuje pożądaną metodę docelową z leksykalnie przechwyconym obiektem:
( defmethod trawers ( funkcja ( a auto )) ;; inny obiekt usunięty ( z gniazdami ( elementy ) a ( dolist ( e elementy ) ( funkcja funcall e )))) ;; stąd też ;; ... ;; alternatywny sposób na print-traverse ( traverse ( lambda ( o ) ( print o
*standardowe wyjście* )) a ) ;; alternatywny sposób zrobienia czegoś z ;; elementy a i liczby całkowitej 42 ( trawers ( lambda ( o ) ( zrób coś o 42 )) a )
Teraz wielokrotne wysyłanie występuje w wywołaniu z treści funkcji anonimowej, więc traverse
jest tylko funkcją mapowania, która rozdziela aplikację funkcji na elementy obiektu. W ten sposób znikają wszystkie ślady Wzorca Odwiedzających, z wyjątkiem funkcji mapowania, w której nie ma dowodów na udział dwóch obiektów. Cała wiedza o tym, że istnieją dwa obiekty i informacja o ich typach, jest zawarta w funkcji lambda.
Przykład Pythona
Python nie obsługuje przeciążania metod w klasycznym tego słowa znaczeniu (zachowanie polimorficzne w zależności od typu przekazywanych parametrów), więc metody „odwiedzające” dla różnych typów modeli muszą mieć różne nazwy.
Źródła
""" Przykład wzorca odwiedzających """ from abc import ABCMeta , abstractmethod NOT_IMPLEMENTED = "Powinieneś to zaimplementować." class CarElement ( metaclass = ABCMeta ): @abstractmethod def accept ( self , odwiedzający ): raise NotImplementedError ( NOT_IMPLEMENTED ) class Body (
CarElement ): def accept ( self , gość ): gość . klasa visitBody ( self ) Silnik ( CarElement ): def accept ( self , gość ): gość . visitEngine ( self ) class Wheel ( CarElement ): def __init__ ( self ,
imię ): sam . name = name def accept ( self , gość ): gość . visitWheel ( self ) class Car ( CarElement ): def __init__ ( self ): self . elementy = [ Koło ( „lewy przedni” ), Koło ( „prawy przedni” ),
Koło ( „lewy tył” ), Koło ( „prawy tył” ), Ciało (), Silnik () ] def accept ( self , gość ): dla elementu w sobie . elementy : pierwiastek . przyjąć ( gościa ) gościa . klasa visitCar ( self ) CarElementVisitor (
metaclass = ABCMeta ): @abstractmethod def visitBody ( self , element ): raise NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitEngine ( self , element ): raise NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitWheel ( self , element ):
raise NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitCar ( self , element ): raise NotImplementedError ( NOT_IMPLEMENTED ) class CarElementDoVisitor ( CarElementVisitor ): def visitBody ( self , body ): print ( „ Przenoszenie mojego ciała . ) def visitCar ( self
, samochód ): print ( " Uruchamianie samochodu." ) def visitWheel ( self , wheel ) : print ( "Kopanie {} koła." .format ( wheel.name ) ) def visitEngine ( self , engine ): print ( " Uruchamiam silnik." ) class CarElementPrintVisitor (
CarElementVisitor ): def visitBody ( self , body ): print ( „Odwiedzające ciało.” ) def visitCar ( self , car ): print ( „Odwiedzający samochód.” ) def visitWheel ( self , wheel ): print ( „Odwiedzające {} koło . . format ( koło .
name )) def visitEngine ( self , engine ): print ( „Odwiedzanie silnika” ) car = Car () car . zaakceptuj ( CarElementPrintVisitor ()) samochód . akceptuj ( CarElementDoVisitor ())
Wyjście
Zwiedzanie przedniego lewego koła. Zwiedzanie przedniego prawego koła. Zwiedzanie tylnego lewego koła. Zwiedzanie tylnego prawego koła. Odwiedzające ciało. Silnik wizytowy. Samochód wizytowy. Kopanie lewego przedniego koła. Kopanie prawego przedniego koła. Kopanie mojego tylnego lewego koła. Kopanie prawego tylnego koła. Poruszając moim ciałem. Uruchamiam mój silnik. Uruchamianie mojego samochodu.
Abstrakcja
Użycie Pythona 3 lub nowszego pozwala na ogólną implementację metody accept:
class Visitable : def accept ( self , gość ): lookup = "visit_" + type ( self ) . __qualname__ . zastąp ( "." , "_" ) return getattr ( gość , szukaj ) ( ja )
Można to rozszerzyć, aby iterować po kolejności rozpoznawania metod klasy, jeśli chcieliby cofnąć się do już zaimplementowanych klas. Mogli również użyć funkcji przechwytywania podklasy, aby wcześniej zdefiniować wyszukiwanie.
Powiązane wzorce projektowe
- Wzorzec iteratora – definiuje zasadę przechodzenia, taką jak wzorzec odwiedzającego, bez rozróżniania typu w obrębie przemierzanych obiektów
- Kodowanie kościelne - pokrewna koncepcja z programowania funkcjonalnego, w której oznaczone typy unii / sum mogą być modelowane przy użyciu zachowań „odwiedzających” na takich typach i która umożliwia wzorcowi odwiedzającemu emulację wariantów i wzorców .
Zobacz też
Linki zewnętrzne
- The Visitor Family of Design Patterns at the Wayback Machine (archiwum z 22 października 2015 r.). Dodatkowe archiwa: 12 kwietnia 2004 , 5 marca 2002 . Przybliżony rozdział z książki The Principles, Patterns, and Practices of Agile Software Development , Robert C. Martin , Prentice Hall
- Wzorzec odwiedzających w UML i LePUS3 (język opisu projektu)
- Artykuł „ Componentization: the Visitor Example autorstwa Bertranda Meyera i Karine Arnout, Computer (IEEE), vol. 39, nr 7, lipiec 2006, strony 23-30.
- Artykuł A Teoretyczna rekonstrukcja wzorca odwiedzających
- Artykuł „ The Essence of Visitor Pattern ” autorstwa Jensa Palsberga i C. Barry'ego Jaya. Dokument IEEE-CS COMPSAC z 1997 r. Pokazujący, że metody accept() są niepotrzebne, gdy dostępna jest refleksja; wprowadza termin „Walkabout” dla techniki.
- Artykuł „ A Time for Reflection ” autorstwa Bruce'a Wallace'a – z podtytułem „Możliwości refleksji Javy 1.2 eliminują uciążliwe metody accept() ze wzorca użytkownika”
- Wzorzec odwiedzającego używający odbicia (java).
- PerfectJPattern Open Source Project , Zapewnia bezkontekstową i bezpieczną dla typów implementację Wzorca Odwiedzających w Javie w oparciu o Delegatów.
- Wzorzec projektowy gościa