Wzorzec
Visitor umożliwia wykonanie takiej samej operacji na każdym obiekcie w złożonej (heterogenicznej) strukturze.
Operacja może być specyficzna dla każdego typu elementu tej struktury.
Innymi słowy:
Visitor oddziela operację/algorytm od struktury na której działa ta operacja.
We wzorcu uczestniczą obiekty implementujące interfejs
IVisitor, który definiuje grupę metod
Visit(), przyjmujące
jako parametry obiekty typów które ma odwiedzić.
Można tu zastosować przeciążenie metody
Visit (np.
void Visit(Apple a),
void Visit(Orange o), ...)
albo oddzielne nazwy metod (
void VisitApple(Apple a),
void VisitOrange(Orange o), ...) - to nie ma większego znaczenia.
Obiekty wchodzące w skład struktury (
ObjectStructure) muszą implementować interfejs, który definiuje jedną metodę:
Accept() przyjmującą jeden parametr: obiekt typu
IVisitor. Dzięki metodzie
Accept() element struktury jest przygotowany na przyjęcie odwiedzającego (Visitora). W metodzie
Accept() wywoływana jest metoda
Visit() obiektu
IVisitor z parametrem this.
Uwagi:
Dokładna postać struktury obiektów może być różna (lista, drzewo, etc.) i nie należy do definicji wzorca.
Zaleta: Można dodawać nowe operacje, bez konieczności modyfikacji klas struktury. Nie trzeba dodawać tej operacji na każdym elemencie
złożonej struktury.
Wada: trudno jest rozbudowywać pierwotną strukturę (bo wtedy w każdym Visitorze trzeba dodać kod specyficzny dla tego nowego elementu). Dlatego najlepiej, gdy jest ona niezmienna.
Przykład (ściągnąłem pomysł pszczoły odwiedzającej kwiaty z książki Thinking in C++ - mam nadzieję, że nikt się nie obrazi). W idealnym przykładzie pszczoła powinna wykonywać jeszcze jakąś operację na kwiatku, gdy go odwiedza, ale to można sobie łatwo wyobrazić.
using System;
using System.Collections.Generic;
namespace VisitorExample
{
class MainClass
{
public static void Main()
{
RandomFlowerGarden garden = new RandomFlowerGarden(5);
Bee bee = new Bee();
garden.Accept(bee);
}
}
interface IVisitor
{
void Visit(Sunflower s);
void Visit(Rose r);
void Visit(Tulip t);
}
class Bee : IVisitor
{
public void Visit(Rose r)
{
// tutaj pszczoła może wykonać jakieś operacje na róży
Console.WriteLine("Pszczoła odwiedza różę");
}
public void Visit(Sunflower s)
{
Console.WriteLine("Pszczoła odwiedza słonecznik");
}
public void Visit(Tulip t)
{
Console.WriteLine("Pszczoła odwiedza tulipana");
}
}
// Drugim odwiedzającym może być np. ważka
//class Dragonfly : IVisitor
// TODO
// Interfejs, który sprawia, że klasa jest "visitor-ready"
interface IFlower
{
void Accept(IVisitor visitor);
}
// Róża
class Rose : IFlower
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
// Tulipan
class Tulip : IFlower
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
// Słonecznik
class Sunflower : IFlower
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
// To jest struktura obiektów - tutaj tablica z różnymi kwiatami
class RandomFlowerGarden
{
private IFlower [] _Flowers ;
public RandomFlowerGarden(int Count)
{
_Flowers = new IFlower[Count];
Random r = new Random(666);
for (int i = 0; i < Count; i++)
{
switch (r.Next(3))
{
case 0 :
_Flowers[i] = new Tulip();
break;
case 1 :
_Flowers[i] = new Rose();
break;
case 2 :
_Flowers[i] = new Sunflower();
break;
}
}
}
public void Accept(IVisitor visitor)
{
foreach (IFlower f in _Flowers)
f.Accept(visitor);
}
}
}
Wszędzie podkreśla się, że wzorzec ten jest rzadko używany, a motywacja jego zastosowania jest bardzo pokrętna.
Daje on bardzo mało korzyści i trzeba się 2 razy zastanowić zanim się go zastosuje.