1. 程式人生 > >visitor-acceptor訪問者設計模式筆記

visitor-acceptor訪問者設計模式筆記

宣告: 這種模式產生的原因是主要解決:穩定的資料結構和易變的操作耦合問題。

在Java中, 一個鮮明的例子: 大家都知道, Collection好象是個黑色大染缸, 本來有各種鮮明型別特徵的物件一旦放入後, 再取出時, 這些型別資訊就消失了(僅僅知道父型別而無法確定具體的子型別). 那麼我們勢必要用If-Else或Switch這種提交結合instanceof來判斷.

訪問者模式適用於資料結構(資料結構多采用Composite模式封裝)相對穩定的系統,它把資料結構和作用於資料結構上的操作之間的耦合解脫開,使得操作集合可以相對自由地演化。訪問者模式實際上是分離了集合中的元素和對這些元素進行的操作。不過使用訪問者模式是有前提的, 儘量確保Visitor很少變化, 當資料結構變化時,他的visitor介面及其實現都要改變.

抽象訪問者(Visitor)角色: 定義介面, 宣告一個或多個訪問操作.
具體訪問者(ConcreteVisitor)角色: 實現抽象訪問者所宣告的介面, 也就是抽象訪問者所宣告的各個訪問操作. 類似一個回撥委託.
抽象被訪問者(Acceptor)角色: 宣告一個接受操作, 接受一個訪問者物件作為一個引數.
具體被訪問者(ConcreteAcceptor)角色: 實現抽象被訪問者所規定的接受操作. 一般是容器集合類的元素節點.
物件結構(ObjectStructure)角色: 可以遍歷結構中的所有元素, 提供一個介面讓訪問者物件都可以訪問每一個元素.

例子1:

public
interface Acceptor { void accept(Visitor visitor); } public interface Visitor { void visit(String str); void visit(int i); void visit(BigInteger integer); } public class Strings implements Acceptor { private String value; public Strings(String value) { this.value = value; } @Override
public void accept(Visitor visitor) { visitor.visit(value); } } public class Integers implements Acceptor { private int value; public Integers(int value) { this.value = value; } @Override public void accept(Visitor visitor) { visitor.visit(value); } } public class BigIntegers implements Acceptor { private BigInteger value; public BigIntegers(BigInteger value) { this.value = value; } @Override public void accept(Visitor visitor) { visitor.visit(value); } } public class DefaultVisitor implements Visitor { @Override public void visit(String str) { System.out.println("String -> " + str); } @Override public void visit(int i) { System.out.println("int -> " + i); } @Override public void visit(BigInteger integer) { System.out.println("BigInteger -> " + integer.toString()); } } public class Main { public static void main(String[] args) { ArrayList<Acceptor> list = new ArrayList<>(); list.add(new Strings("66666")); list.add(new Integers(3234)); list.add(new BigIntegers(new BigInteger("232342324"))); DefaultVisitor visitor = new DefaultVisitor(); for (Acceptor acceptor : list) { acceptor.accept(visitor); } } }

例子2:

public interface Acceptor {
    void accept(Visitor visitor);
    String getType();
}

public interface Visitor {
    void visit(Strings str);
    void visit(Integers i);
    void visit(BigIntegers integer);
}

public class Strings implements Acceptor {
    private String value;

    public Strings(String value) {
        this.value = value;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    @Override
    public String getType() {
        return "Strings";
    }

    public String getString() {
        return value;
    }
}

public class Integers implements Acceptor {
    private int value;

    public Integers(int value) {
        this.value = value;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    @Override
    public String getType() {
        return "Integers";
    }

    public int getInteger() {
        return value;
    }
}

public class BigIntegers implements Acceptor {
    private BigInteger value;

    public BigIntegers(BigInteger value) {
        this.value = value;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    @Override
    public String getType() {
        return "BigIntegers";
    }

    public BigInteger getBigInteger() {
        return value;
    }
}

public class VisitorAdapter implements Visitor {
    @Override
    public void visit(Strings str) {
        // no-op
    }

    @Override
    public void visit(Integers i) {
        // empty-implementation
    }

    @Override
    public void visit(BigIntegers integer) {
        // do nothing
    }
}

public class DefaultVisitor extends VisitorAdapter {
    @Override
    public void visit(Strings str) {
        System.out.println(str.getType() + " -> " + str.getString());
    }

    @Override
    public void visit(Integers i) {
        System.out.println(i.getType() + " -> " + i.getInteger());
    }
}

public class BigIntegerVisitor extends VisitorAdapter {
    @Override
    public void visit(BigIntegers i) {
        System.out.println(i.getType() + " -> " + i.getBigInteger());
    }
}

public class ObjectStructure {
    private final List<Acceptor> list = new ArrayList<>();

    public void add(Acceptor acceptor) {
        list.add(acceptor);
    }

    public void remove(Acceptor acceptor) {
        list.remove(acceptor);
    }

    public void visitAll(Visitor visitor) {
        for (Acceptor acceptor : list) {
            acceptor.accept(visitor);
        }
    }
}

public class Client {
    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.add(new Strings("66666"));
        objectStructure.add(new Integers(3234));
        objectStructure.add(new BigIntegers(new BigInteger("232342324")));
        System.out.println("DefaultVisitor-----------------------");
        Visitor visitor = new DefaultVisitor();
        objectStructure.visitAll(visitor);
        System.out.println("BigIntegerVisitor-----------------------");
        visitor = new BigIntegerVisitor();
        objectStructure.visitAll(visitor);
    }
}

仔細比對例1和例2, 通過VisitorAdapter, 能適度減弱訪問者的缺陷, 如果沒有它, 增加一個被訪問者節點, 將影響所有具體訪問者.

引用陳詞(http://www.cnblogs.com/zhenyulu/articles/79719.html)

面向物件的設計原則中最重要的便是所謂的”開一閉”原則。一個軟體系統的設計應當儘量做到對擴充套件開放,對修改關閉。達到這個原則的途徑就是遵循”對變化的封裝”的原則。

很多系統可以按照演算法和資料結構分開,也就是說一些物件含有演算法,而另一些物件含有資料,接受演算法的操作。如果這樣的系統有比較穩定的資料結構,又有易於變化的演算法的話,使用訪問者模式就是比較合適的,因為訪問者模式使得演算法操作的增加變得容易。

反過來,如果這樣一個系統的資料結構物件易於變化,經常要有新的資料物件增加進來的話,就不適合使用訪問者模式。因為在訪問者模式中增加新的節點很困難,要涉及到在抽象訪問者和所有的具體訪問者中增加新的方法。

之所以備註這篇筆記, 是在研究ANTLR的時候, 突然看明白了visitor模式, 然後我上網查考了很多這方面的部落格(已經不記得有多少了), 將有感觸的文字拷貝了過來. 除了兩個例子, 無原創, 也不指著這個盈利或出名, 如果作者不幸看到, 恰巧又不太高興, 請直接找我吧, 感謝您的文字!