Visitor

Návrhový vzor visitor umožňuje pro danou skupinu tříd dynamicky definovat nové operace, aniž by bylo nutné tyto třídy jakkoliv modifikovat. Visitor řadíme mezi vzory chování (behavioral) a patří mezi Gang of Four (GoF) vzory (je obsažen v bibli návrhových vzorů Design Patterns: Elements of Reusable Object-Oriented Software).

Implementace

Vzor visitor obsahuje dva typy tříd – třídy, které jsou navštěvované a třídy navštěvující (visitory). Navštěvované třídy v sobě nesou základní funkcionalitu, navštivující naopak přinášejí dynamicky (tj. za běhu aplikace) všem navštěvovaným třídám dodatečné chování.

Každá navštěvovaná třída musí být na příchod návštěvníka připravena – musí mít metodu (obvykle pojmenovanou accept) – která návštěvníka přijme a předá mu data, která potřebuje ke své práci. Jedná se o ukazatel na současnou instanci (this) a datové proměnné, ke kterým nemá návštěvník přístup (jsou například soukromé). Odkaz na aktuální instanci neslouží pouze k přenosu dat, rozhoduje také o tom, která metoda návštěvníka bude zavolána.


Příklad

Představme si, že máme třídy obecního úřadu a krajské policejní správy (obě třídy dědí z třídy AbstractAuthority), dále máme návštěvníka, který přidává oběma úřadům schopnost vydávat výpis z rejstříku trestů. V okamžiku, kdy si občan zažádá o výpis z rejstříku obecní úřad, tak třída úřadu přijme odpovídajícího návštěvníka a předá mu potřebnou infrastrukturu. Metoda návštěvníka pro obecní úřad zjistí, která úřednice má nejméně práce a přidá jí vyřízení žádosti do úkolů na další den. Pokud by občan přišel na krajskou policejní, tak bude volána metoda specifická pro třídu (systém) policejní správy, která využije místní databáze a vrátí navštěvované třídě požadovaný dokument (výpis).

Schéma volání návštěvníků
Schéma volání návštěvníků

Double dispatch

Ač se to není úplně patrné, tak nelze tento vzor implementovat jednodušeji – předat přetížené metodě visitor.visit abstraktního předka obou úřadů (AbstractAuthority) s tím, že se za běhu rozhodne dle skutečného typu parametru, zda-li bude volána metoda pro obecní úřad nebo policejní správu. Značná část programovacích jazyků totiž rozhoduje volání přetížených metod během kompilace. Pokud by tedy byl parametrem volání abstraktní předek, tak by se program ani nepřeložil (nešlo by rozhodnout, která metoda se má volat). Pokud by existovala varianta metody pro tohoto předka, tak by se program sice přeložil, ale byla by vždy volána jen pouze tato implementace (bez ohledu na to, že skutečným parametrem je jeden z potomků).

Tento nedostatek řeší výše uvedené double-dispatch schéma, ve kterém je nejprve volána metoda accept, která návštěvníkovi předá odkaz na aktuální instanci, pomocí něhož může bez problémů překladač rozhodnout, kterou variantu metody visit zavolat.

Rozšiřitelnost

Do programu, který využívá vzoru visitor je velmi snadné přidat novou funkcionalitu, jelikož stačí vytvořit nového návštěvníka. Kdybychom chtěli přidat úřadům schopnost vydávat pasy, tak bychom jen vytvořili třídu PassportVisitor, která by měla metodu visit přetíženou pro oba typy úřadu a implementovala pro ně specifickou funkcionalitu.

Z opačné strany je velmi obtížné vytvořit novou rozšiřovanou třídu, jelikož to znamená úpravu všech stávajících návštěvníků – přidání odpovídající přetížené varianty metody visit.

Diagram

Visitor - UML diagram
Visitor - UML diagram

Kód

/**
 * Demonstrace navrhoveho vzoru Visitor
 * @author malejpavouk
 */
public class VisitorDemonstration {
    public static void main(String[] args) {
        AbstractElement e = new BarElement(); //navstevovany objekt 1
        AbstractElement e2 = new ZooElement(); //navstevovany objekt 2
        VisitorInterface v = new FooVisitor(); //visitor pridavajici operaci foo

        e.accept(v);
        System.out.println(""); //odradkovani vystupu        
        e2.accept(v);
    }
}
/**
 * Abstraktni element
 * @author malejpavouk
 */
abstract class AbstractElement {
    /**
     * Prijmi daneho visitora (metoda s promennym poctem parametru, do metody
     * budou parametry predany jako pole params)
     * @param v visitor
     * @param params parametry volani visitora
     */
    public abstract Object[] accept(VisitorInterface v, Object... params);
}
/**
 * Navstevovana trida 1
 * @author malejpavouk
 */
class BarElement extends AbstractElement {
    public Object[] accept(VisitorInterface v, Object... params) {
        //zde muze objekt pridat do pole parametru nejake sve dodatecne vlastnosti
        System.out.println("BarElement: Adding some of my private field values to the params array");
        Object[] result = v.visit(this, params);
        //zde na zaklade vystupu metody muze element modifikovat svuj vnitrni stav
        //(soukromou cast - k te nemela metoda visitora pristup)
        System.out.println("BarElement: Modifying my inner state");

        //skutecnym vystupem procesu muze byt pouze prvni prvek pole, ktere
        //generoval visitor
        Object[] newResult = {result[0]};

        System.out.println("BarElement: Returning the call result: \\"" + result[0] + "\\"");
        return newResult;
    }
    /**
     * Udela operaci bar
     */
    public void bar() {
        System.out.println("bar!!!");
    }
}
/**
 * Navstevovana trida
 * @author malejpavouk
 */
class ZooElement extends AbstractElement {
    public Object[] accept(VisitorInterface v, Object... params) {
        //zde muze objekt pridat do pole parametru nejake sve dodatecne vlastnosti
        System.out.println("ZooElement: Adding some of my private field values to the params array");
        Object[] result = v.visit(this, params);
        //zde na zaklade vystupu metody muze element modifikovat svuj vnitrni stav
        //(soukromou cast - k te nemela metoda visitora pristup)
        System.out.println("ZooElement: Modifying my inner state");

        //skutecnym vystupem procesu muze byt pouze prvni prvek pole, ktere
        //generoval visitor
        Object[] newResult = {result[0]};

        System.out.println("BarElement: Returning the call result: \\"" + result[0] + "\\"");
        return newResult;
    }
    /**
     * Udela operaci zoo
     */
    public void zoo() {
        System.out.println("zoo!!!");
    }
}
/**
 * Interface navstevnika
 * @author malejpavouk
 */
interface VisitorInterface {
    /**
     * Operace pridavana k bar elementu
     * @param e barelement, ke kteremu bude operace pridana
     * @param params parametry volani
     * @return vystup dane operace
     */
    public Object[] visit(BarElement e, Object... params);
    /**
     * Operace pridavana k zoo elementu
     * @param e zooelement, ke kteremu bude operace pridana
     * @param params parametry volani
     * @return vystup dane operace
     */
    public Object[] visit(ZooElement e, Object... params);
}
/**
 * Navstevnik, ktery udela operaci foo na danem elementu
 * @author malejpavouk
 */
class FooVisitor implements VisitorInterface {
    /**
     * Tato operace bude pridana k bar elementu. Metoda ma variabilni pocet argumentu.
     * Tyto argumenty slouzi k predani vsech parametru, ktere metoda potrebuje (muze
     * se tak napriklad jednat i o soukrome vlastnosti bar elementu, ke kterym by jinak
     * nemela metoda pristup (pokud by nevyuzivala reflexi))
     * @param e barelement, ke kteremu bude metoda pridana
     * @param params parametry volani
     * @return navratova hodnota daneho volani
     */
    public Object[] visit(BarElement e, Object... params) {
        System.out.println("FooVisitor: Visiting " + e.getClass().toString());
        System.out.println("FooVisitor: foo method is working on the visited BarElement");
        System.out.println("FooVisitor: Modifying public BarElement state");

        System.out.println("FooVisitor: Producing method result");
        Object[] result = {"Foo visitor method return state", "Params for visited element", new Object()};
        return result;
    }
    /**
     * Tato operace bude pridana k zoo elementu. Metoda ma variabilni pocet argumentu.
     * Tyto argumenty slouzi k predani vsech parametru, ktere metoda potrebuje (muze
     * se tak napriklad jednat i o soukrome vlastnosti zoo elementu, ke kterym by jinak
     * nemela metoda pristup (pokud by nevyuzivala reflexi))
     * @param e barelement, ke kteremu bude metoda pridana
     * @param params parametry volani
     * @return navratova hodnota daneho volani
     */
    public Object[] visit(ZooElement e, Object... params) {
        System.out.println("FooVisitor: Visiting " + e.getClass().toString());
        System.out.println("FooVisitor: Doing something zooable");
        System.out.println("FooVisitor: foo method is working on the visited ZooElement");
        System.out.println("FooVisitor: Modifying public ZooElement state");

        System.out.println("FooVisitor: Producing method result");
        Object[] result = {"Foo visitor method return state", "Params for visited element", new Integer(5)};
        return result;
    }
}
BarElement: Adding some of my private field values to the params array
FooVisitor: Visiting class algoritmy.designpatterns.Visitor.BarElement
FooVisitor: foo method is working on the visited BarElement
FooVisitor: Modifying public BarElement state
FooVisitor: Producing method result
BarElement: Modifying my inner state
BarElement: Returning the call result: "Foo visitor method return state"

ZooElement: Adding some of my private field values to the params array
FooVisitor: Visiting class algoritmy.designpatterns.Visitor.ZooElement
FooVisitor: Doing something zooable
FooVisitor: foo method is working on the visited ZooElement
FooVisitor: Modifying public ZooElement state
FooVisitor: Producing method result
ZooElement: Modifying my inner state
ZooElement: Returning the call result: "Foo visitor method return state"







Doporučujeme