úterý 17. května 2011

Visitor prakticky

Jedním z mých oblíbených návrhových vzorů je Visitor, bohužel se nesetkávám v moc často s jeho použitím. Jako u většinou návrhových vzorů snad kromě totálně provařeného Singletonu to vypadá tak, že teoreticky všichni známe všechny vzory a prakticky umíme použít jenom ten jeden nebo dva. Tenhle článek je moje praktické připomenutí tohoto k čemu tento vzor používám.

Visitor používám pro případy, kdy mám košatou objektovou strukturu se kterou potřebuji pracovat. Typicky se jedná o operace: projdi strukturu a najdi všechny objekty vyhovující danému kritérie nebo projdi strukturu a někde něco pozměň.

Mějme objekty Tree, Branch a Leaf, které se agregují. To znamená, že Tree má N Branch a ta zase N Leaf.

Klíčové je uvědomit si, jak bude vypadat kód jakékoliv operace, kterou budeme chtít nad touto strukturou udělat. Ve všech případech tam bude jeden a ten samý kód, který tuto strukturu bude prolézat a potom tam bude malé množství kódu, které provede vlastní operaci s danou strukturou.

To je přímo přesné zadání pro aplikaci návrhového vzoru Visitor. Společný kód, tedy hierarchické prolézání struktury do hloubky, si bude implementovat každý objekt v dané agregaci sám. Operace nad každým jednotlivým objektem bude delegována na Visitor, který bude předán jako argument metody.

To úžasné na tomto vzoru je, že jakékoliv operace nad strukturou mohou být přidávány aniž bychom zasahovali do původních objektů. Zároveň mohou být být čistě zapouzdřeny do objektů. Odpadá nám špagetový kód (procedurální) kód, ve kterém bychom míchali prolézání struktury s danou operací nad ní. Máme čisté oddělení odpovědností. Díky tomu se nám zjednoduší testování, což je skoro vždy důkaz dobrého objektového návrhu.

Řekněme, že chceme napsat operace, která nám každý druhý lístek na stromě obarví na žlutou barvu.

    public class Marker implements Visitor{
        private int leafCounter = 1;

        @Override
        public void visitLeaf(final Leaf leaf) {
            if(leafCounter % 2 == 0) {
                leaf.setColor("yellow");
            }
            leafCounter++;
        }
    }

Test je pak velice jednoduchý (využívám knihovnu Mockito)

    import static org.mockito.Matchers.anyString;
    import static org.mockito.Matchers.eq;
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.never;
    import static org.mockito.Mockito.verify;

    import org.junit.Test;
    public class MarkerTest {

        @Test
        public void testVisitLeaf() {
            Leaf leaf = mock(Leaf.class);
            Marker marker = new Marker();
            marker.visitLeaf(leaf);
            verify(leaf, never()).setColor(anyString());
            marker.visitLeaf(leaf);
            verify(leaf).setColor(eq("yellow"));
        }

    }

Použití konkrétního visitoru pak vypadá následovně:

    Tree tree = new Tree();
    Marker marker = new Marker();
    tree.accept(marker);

Zdrojové soubory jsou k dispozici na GitHub.