Visitor

Situace

Je definována hiearchie tříd se společným předkem. Tento předek deklaruje metodu, která je v každé podtřídě implementována jinak. Zdrojový kód všech konkrétních implementací se má z hiearchie extrahovat a přesunout jinam, například do jedné externí třídy.

diagram tříd popisující výchozí situaci

diagram tříd popisující požadovanou cílovou situaci

Problém

V programovacích jazycích, které podporují tzv. multiple dispatch, stačí využít přetěžování metod. Metoda se přetíží pro každou podtřídu a výběr té správné metody zajistí již jazyk automaticky. V ostatních jazycích (které podporují pouze single dispatch) se tento mechanizmus musí simulovat.

Externí třída, do které byly implementace přesunuty, veřejně vystaví pouze tu nejobecnější možnou metodu – tedy tu, která jako parametr přijímá kořenovou třídu hiearchie. Tato metoda pak musí podle konkrétního typu parametru zvolit správnou metodu a spustit ji.

Triválně se tento problém řeší kaskádou podmínek if, které za běhu ověřují typ parametru a podle něho volají konkrétní metodu.

kód v jazyce Java - Zobrazit

  1. public void doSomething(Parent a)
  2. {
  3.   if (a instanceof Child1)
  4.   {
  5.     // zavolat implementaci pro třídu Child1
  6.     this.doSomething((Child1) a);
  7.   }
  8.   else if (a instanceof Child2)
  9.   {
  10.     // zavolat implementaci pro třídu Child2
  11.     this.doSomething((Child3) a);
  12.   }
  13.   else if (a instanceof Child3)
  14.   {
  15.     // zavolat implementaci pro třídu Child3
  16.     this.doSomething((Child3) a);
  17.   }
  18.   else
  19.   {
  20.     throw new IllegalArgumentException();
  21.   }
  22. }
  23.  
  24. protected void doSomething(Child1 a) {...}
  25. protected void doSomething(Child2 a) {...}
  26. protected void doSomething(Child3 a) {...}

Tento způsob ale není příliš dobrý, protože je značně nepřehledný a podmínky se musí udržovat konzistentní s aktuální hiearchií odpovídajících tříd. Navíc se tak zbytečně řeší problém, který by teoreticky vzniknout ani nemusel.

Řešení

Obtížně udržovatelná a těžce srozumitelná kaskáda podmínek if se nahradí elegantní spoluprací dvou tříd, které jsou zde označeny jako Place a Visitor. Třída Place požádá vybranou instanci třídy Visitor o provedení akce a předá ji sama sebe jako parametr. Třída Visitor pak na této instanci zavolá požadovanou metodu, která odpovídá její třídě.

Takto se simuluje double dispatch v jazycích, které ho nemají. V principu se k identitě příjemce metody přidává ještě druhá a chybějící informace o odesílateli.

UML diagramy

diagram tříd

sekvenční diagram

Příklad

Následuje jednoduchý příklad implementace tohoto vzoru v programovacím jazyce Java.

Cíle návštěv

Cíle návštěv budou dva. První z nich bude kino, druhé muzeum. Oba cíle budou implementovat společné rozhraní.

kód v jazyce Java - Zobrazit

  1. /**
  2.  * Nějaké zajímavé místo.
  3.  *
  4.  * @author Vojtěch Hordějčuk
  5.  */
  6. public interface Place
  7. {
  8.   /**
  9.    * Přišel návštěvník.
  10.    *
  11.    * @param visitor
  12.    * návštěvník
  13.    */
  14.   public void accept(Visitor visitor);
  15. }
  16.  
  17. /**
  18.  * Kino.
  19.  *
  20.  * @author Vojtěch Hordějčuk
  21.  */
  22. public class Cinema implements Place
  23. {
  24.   @Override
  25.   public void accept(Visitor visitor)
  26.   {
  27.     System.out.println("do kina přišel " + visitor.toString());
  28.     visitor.visit(this);
  29.   }
  30. }
  31.  
  32. /**
  33.  * Muzeum.
  34.  *
  35.  * @author Vojtěch Hordějčuk
  36.  */
  37. public class Museum implements Place
  38. {
  39.   @Override
  40.   public void accept(Visitor visitor)
  41.   {
  42.     System.out.println("do muzea přišel " + visitor.toString());
  43.     visitor.visit(this);
  44.   }
  45. }
Návštěvníci

Návštěvníci budou dva. První z nich bude představovat hodného návštěvníka, druhý zlého. Oba budou implementovat společné rozhraní.

kód v jazyce Java - Zobrazit

  1. /**
  2.  * Obecný návštěvník.
  3.  *
  4.  * @author Vojtěch Hordějčuk
  5.  */
  6. public interface Visitor
  7. {
  8.   /**
  9.    * Navštíví zadaný koncert.
  10.    *
  11.    * @param concert
  12.    * cílový koncert
  13.    */
  14.   public void visit(Museum concert);
  15.  
  16.   /**
  17.    * Navštíví zadané kino.
  18.    *
  19.    * @param cinema
  20.    * cílové kino
  21.    */
  22.   public void visit(Cinema cinema);
  23. }
  24.  
  25. /**
  26.  * Hodný návštěvník.
  27.  *
  28.  * @author Vojtěch Hordějčuk
  29.  */
  30. public class GoodVisitor implements Visitor
  31. {
  32.   @Override
  33.   public void visit(Museum museum)
  34.   {
  35.     System.out.println(this.toString() + " je v muzeu");
  36.   }
  37.  
  38.   @Override
  39.   public void visit(Cinema cinema)
  40.   {
  41.     System.out.println(this.toString() + " je v kině");
  42.   }
  43.  
  44.   @Override
  45.   public String toString()
  46.   {
  47.     return "pan Hodný";
  48.   }
  49. }
  50.  
  51. /**
  52.  * Zlý návštěvník.
  53.  *
  54.  * @author Vojtěch Hordějčuk
  55.  */
  56. public class EvilVisitor implements Visitor
  57. {
  58.   @Override
  59.   public void visit(Museum museum)
  60.   {
  61.     System.out.println(this.toString() + " vykradl muzeum.");
  62.   }
  63.  
  64.   @Override
  65.   public void visit(Cinema cinema)
  66.   {
  67.     System.out.println(this.toString() + " udělal nepořádek v kině.");
  68.   }
  69.  
  70.   @Override
  71.   public String toString()
  72.   {
  73.     return "pan Zlý";
  74.   }
  75. }
Test

Vytvoří se oba druhy návštěvníků. Poté se vytvoří i obě cílová místa. Nakonec se návštěvníci pošlou do obou cílů.

kód v jazyce Java - Zobrazit

  1. public static void main(String[] args)
  2. {
  3.   // vytvořit návštěvníky
  4.  
  5.   Visitor good = new GoodVisitor();
  6.   Visitor evil = new EvilVisitor();
  7.  
  8.   // vytvořit cíle
  9.  
  10.   Place cinema = new Cinema();
  11.   Place museum = new Museum();
  12.  
  13.   // hodný návštěvník
  14.  
  15.   cinema.accept(good);
  16.   museum.accept(good);
  17.  
  18.   // zlý návštěvník
  19.  
  20.   cinema.accept(evil);
  21.   museum.accept(evil);
  22. }

Reference