设计模式之访问者模式

前言

访问者模式在23种设计模式应该算是复杂的,因此在解释定义前,先以实际的例子带入一下觉得理解起来会更好一些。

 

场景:很多人都有养宠物的习惯,这里就以此为例

  • 访问者角色:给宠物喂食的人
  • 具体访问者角色:主人、其他人
  • 抽象元素角色:动物抽象类
  • 具体元素角色:宠物狗、宠物猫
  • 结构对象角色:主人家

结构图如下:

设计模式之访问者模式

 

1.创建抽象访问者接口

interface Person {

    void feed(Cat cat);

    void feed(Dog dog);
}

2.创建不同的具体访问者角色 -- 主人/其他人,同时实现 Person接口

class Owner implements Person {

    @Override
    public void feed(Cat cat) {
        System.out.println("主人喂食猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("主人喂食狗");
    }
}

class Someone implements Person {

    @Override
    public void feed(Cat cat) {
        System.out.println("其他人喂食猫");
    }

    @Override
    public void feed(Dog dog) {
        System.out.println("其他人喂食狗");
    }
}

3.创建 抽象节点 -- 宠物

interface Animal {

    void accept(Person person);
}

4.创建实现Animal接口的 具体节点(元素)

class Dog implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("好好吃,汪汪汪!!!");
    }
}


/**
 * 具体节点(元素)角色 -- 宠物猫
 */
class Cat implements Animal {

    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("好好吃,喵喵喵!!!");
    }
}

5.创建结构对象角色

class Home {
    private List<Animal> nodeList = new ArrayList<>();

    void action(Person person) {
        for (Animal node : nodeList) {
            node.accept(person);
        }
    }

    /**
     * 添加操作
     *
     * @param animal 动物
     */
    void add(Animal animal) {
        nodeList.add(animal);
    }
}

6.创建客户端,用于测试

public class Client {

    public static void main(String[] args) {
        Home home = new Home();
        home.add(new Dog());
        home.add(new Cat());

        Owner owner = new Owner();
        home.action(owner);

        Someone someone = new Someone();
        home.action(someone);
    }

}

7.运行结果

主人喂食狗
好好吃,汪汪汪!!!
主人喂食猫
好好吃,喵喵喵!!!
其他人喂食狗
好好吃,汪汪汪!!!
其他人喂食猫
好好吃,喵喵喵!!!

访问模式的定义

访问者模式(Visitor)表示 一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为模式。

访问模式角色

 Visitor 抽象访问者

抽象访问者。为该对象结构中的ConcreteElement的每一个类声明的一个操作。

ConcreteVisitor 具体访问者

具体访问者。实现Visitor申明的每一个操作,每一个操作实现算法的一部分。 

Element  抽象元素

元素接口,它定义了一个接受访问者(accept)的方法,其意义是指,每一个元素都要可以被访问者访问。

ConcreteElement 具体元素

具体的元素类,它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。

ObjectStructure 对象结构角色

这个便是定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。

访问模式优缺点 

优点:

新增访问操作变得更加简单。
能够使得用户在不修改现有类的层次结构下,定义该类层次结构的操作。
将有关元素对象的访问行为集中到一个访问者对象中,而不是分散搞一个个的元素类中。

缺点:

增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,违背了“开闭原则”的要求。 

破坏封装。当采用访问者模式的时候,就会打破组合类的封装。

访问模式的应用场景

一个对象结构包含很多类对象,他们有不同的接口,想对这些对象实施一些依赖于其具体类的操作,也就是用迭代器模式已经不能胜任的场景。

访问模式的结构图

             设计模式之访问者模式

 

访问模式基本代码

抽象访问者

public abstract class Visitor {

    public abstract void visitConcreteElementA(ConcreteElementA concreteElementA);

    public abstract void visitConcreteElementB(ConcreteElementB concreteElementB);
}

具体访问者

public class ConcreteVisitor1 extends Visitor {
    @Override
    public void visitConcreteElementA(ConcreteElementA concreteElementA) {
        System.out.println("ConcreteVisitor1:"+concreteElementA.operationA());
    }

    @Override
    public void visitConcreteElementB(ConcreteElementB concreteElementB) {
        System.out.println("ConcreteVisitor1:"+concreteElementB.operationB());
    }
}
public class ConcreteVisitor2 extends Visitor {
    @Override
    public void visitConcreteElementA(ConcreteElementA concreteElementA) {
        System.out.println("ConcreteVisitor2:"+concreteElementA.operationA());
    }

    @Override
    public void visitConcreteElementB(ConcreteElementB concreteElementB) {
        System.out.println("ConcreteVisitor2:"+concreteElementB.operationB());
    }
}

抽象元素

public abstract class Element {

    public abstract void accept(Visitor visitor);
}

 具体元素

public class ConcreteElementA extends Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visitConcreteElementA(this);
    }

    public String operationA() {
        return "ConcreteElementA";
    }
}
public class ConcreteElementB extends  Element {

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

    public String operationB() {
        return "ConcreteElementB";
    }
}

 对象结构角色

public class ObjectStructure {

    private List<Element> elments = new LinkedList<Element>();

    // 新增
    public void attach(Element element) {
        elments.add(element);
    }
    // 删除
    public void detach(Element element) {
        elments.remove(element);
    }

    public  void accept(Visitor visitor) {
        for (Element elment:elments ) {
            elment.accept(visitor);
        }
    }
}

测试类

public class Test {

    public static void main(String[]args) {
        ObjectStructure o = new ObjectStructure();
        o.attach(new ConcreteElementA());
        o.attach(new ConcreteElementB());

        ConcreteVisitor1 concreteVisitor1 = new ConcreteVisitor1();
        ConcreteVisitor2 concreteVisitor2 = new ConcreteVisitor2();
        o.accept(concreteVisitor1);
        o.accept(concreteVisitor2);
    }
}

 运行结果

ConcreteVisitor1:ConcreteElementA
ConcreteVisitor1:ConcreteElementB
ConcreteVisitor2:ConcreteElementA
ConcreteVisitor2:ConcreteElementB

总结

所以GoF四人中的一个作者说过:‘大多时候你并不需要访问者模式,但一旦你需要访问者模式时,那就是真的需要它了。’事实上我们很难找到数据结构不变化的情况,所以用访问者模式的机会也就不太多了。