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

Przykładowy diagram klas UML i diagram sekwencji dla wzorca projektowego Visitor.


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

Gość w LePUS3 ( legenda )

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

UML diagram of the Visitor pattern example with Car Elements

Ź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