设计模式——访问者模式

945 阅读4分钟

一、概述

访问者模式即Visitor模式,可以在不修改已有类的情况下,增加新的操作,使得分离对象的数据和行为,访问者模式也是行为型模式。访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。

概念看起来很难懂,举个具体的例子,说到访客,平时我们家里会有亲戚朋友过来串门,亲戚朋友就是访客,他们可以做一些事,也不能做全部的事,比如能吃饭,看电视,但是不能睡觉,洗碗;再比如我们去各种景点、博物馆游玩,那我们就是访客,我们可以游览、购物,但是不能搞破坏等等。

访问者模式主要由五个角色组成:

  • 抽象访问者(Visitor):高度抽象的接口,其中声明了一个或者多个方法操作,是所有的具体访问者角色父接口。
  • 具体访问者(ConcreteVisitor):实现抽象访问者的接口,在抽象的方法中实现自己具体的逻辑。
  • 抽象节点(Node):声明一个接受操作,接受一个访问者对象作为一个参数。
  • 具体节点(ConcreteNode):实现了抽象节点所规定的接受操作。
  • 结构对象(ObjectStructure):可以遍历结构中的所有元素。

那么我们就以朋友串门来作为具体的例子说明。

二、使用

首先创建抽象的访问者Visitor,访问者可以吃饭、看电视这些具体的节点。

/**
 * 抽象访问者,访问者可以在家里吃饭、看电视
 */
public interface Visitor {
    void visit(Eat eat);
    void visit(WatchTV watchTV);
}

再来看抽象节点。以家作为抽象节点的根据,里面有能做什么事的抽象方法。

/**
 * 抽象的节点
 */
public interface Home {
    void canDo(Visitor visitor);
}

然后就是具体的节点,表示能做的具体事情。

/**
 * 吃饭
 */
public class Eat implements Home{

    private static final String TAG = "XXX";

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

    public void eat(String name){
        Log.e(TAG, name + ",吃饭");
    }
}
/**
 * 看电视
 */
public class WatchTV implements Home{

    private static final String TAG = "XXX";

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

    public void watch(String name){
        Log.e(TAG, name + ",看电视");
    }
}

再然后就是具体的访问者角色了,这里创建了张三、李四这两个访问者。

/**
 * 具体的访问者——张三
 */
public class ZhangSanVisitor implements Visitor {

    private String name;

    public ZhangSanVisitor(String name){
        this.name = name;
    }

    @Override
    public void visit(Eat eat) {
        eat.eat(name);
    }

    @Override
    public void visit(WatchTV watchTV) {
        watchTV.watch(name);
    }
}
/**
 * 具体的访问者——李四
 */
public class LiSiVisitor implements Visitor{

    private String name;

    public LiSiVisitor(String name){
        this.name = name;
    }

    @Override
    public void visit(Eat eat) {
        eat.eat(name);
    }

    @Override
    public void visit(WatchTV watchTV) {
        watchTV.watch(name);
    }
}

最后就是结构对象,此类用来遍历结构中的所有元素。

/**
 * 结构对象
 */
public class ObjectStructure {
    private List<Home> homeList = new ArrayList<>();

    public void addObj(Home home){
        homeList.add(home);
    }

    public void doAction(Visitor visitor){
        for(Home home : homeList){
            home.canDo(visitor);
        }
    }
}

来测试一下,首先创建了结构对象,向其中添加了两个节点,即吃饭和看电视,接着创建一个具体的访问者——张三,在结构对象中遍历访问者能做哪些事情。打印输出如下如所示。

ObjectStructure objectStructure = new ObjectStructure();
objectStructure.addObj(new Eat());
objectStructure.addObj(new WatchTV());
Visitor visitor = new ZhangSanVisitor("张三");
objectStructure.doAction(visitor);

Visitor visitor1 = new LiSiVisitor("李四");
objectStructure.doAction(visitor1);

三、总结

从上面的代码中可以看到,访问者模式的扩展性很好,可以在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能,如果有新的访问者——王五,直接实现访问者接口既即可;符合单一职责原则,通过访问者将无关的行为分离,使职责单一。

访问者模式也有一些缺点:违反了迪米特原则,因为具体元素对访问者公布细节;也违反了依赖倒置原则,依赖了具体类,没有依赖抽象,可以看到在访问者接口中引用了具体的节点;对象结构变化困难,若对象结构发生了改变,访问者的接口和访问者的实现也都要发生相应的改变;

github地址:github.com/leewell5717…

四、参考

Java进阶篇设计模式之十 ---- 访问者模式和中介者模式