likes
comments
collection
share

设计模式-访问者模式

作者站长头像
站长
· 阅读数 23

# 七大软件设计原则 # 设计模式-工厂模式 # 设计模式-单例模式 # 设计模式-原型模式 # 设计模式-策略模式 # 设计模式-责任链模式 # 设计模式-建造者模式 # 设计模式-深度分析代理模式 # 设计模式-门面模式 # 设计模式-装饰器模式 # 设计模式-享元模式 # 设计模式-组合模式 # 设计模式-适配器模式 # 设计模式-桥接模式 # 设计模式-委派模式 # 设计模式-模板方法模式 # 设计模式-迭代器模式 # 设计模式-命令模式 # 设计模式-备忘录模式 # 设计模式-状态模式 # 设计模式-中介者模式 # 设计模式-解释器模式

访问者模式(Visitor Pattern)是一种将数据结构与数据操作分离的设计模式。是指封装一 些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些 元素的新的操作。属于行为型模式。

访问者模式的基本思想是,针对系统中拥有固定类型数的对象结构(元素),在其内提供一个accept()方法用来接受访问者对象的访问。不同的访问者对同一元素的访问内容不同,使得相同的元素集合可以产生不同的数据结果。accept()方法可以接收不同的访问者对象,然后在内部将自己(元素)转发到接收到的访问者对象的visit()方法内。访问者内部对应类型的 visit()方法就会得到回调执行,对元素进行操作。也就是通过两次动态分发(第一次是对访问者的分发 accept()方法,第二次是对元素的分发 visit()方法,才最终将一个具体的元素传递到一个具体的访问者。如此一来,就解耦了数据结构与操作,且数据操作不会改变元素状态。访问者模式的核心是,解耦数据结构与数据操作,使得对元素的操作具备优秀的扩展性。可以通过扩展不同的数据操作类型(访问者)实现对相同元素集的不同的操作。

访问者模式在现实中的场景

通过上文的描述可能某些人还是很糊涂,这里我们拿一个生活中的例子来说明。 比如我们去动物园,动物园中有很多场馆、熊猫馆、企鹅馆等等,这些馆项基本上都是固定的很少变动的,然后每个馆的门票又不一样,针对不同的访问者比如学生、军人、大众又有不同的折扣,如果这个票价写在 “馆” 这个类中那我们岂不是要写很多ifelse,针对不同的人返回不同的价格,假设又要增加一种访问者又要修改原方法不符合开闭原则,可能有人说我们可以通过添加类继承的方式来实现,那你要多少个类啊是吧,所以此时就比较适用于访问者模式, 所以当系统中存在类型数目稳定(固定)的一类数据结构时,可以通过访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会数据产生任何副作用(脏数据)。

总结一下访问者模式的适用场景如下:

  1. 数据结构稳定,作用于数据结构的操作经常变化的场景;
  2. 需要数据结构与数据操作分离的场景;
  3. 需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景。

UML类图

设计模式-访问者模式 我们只看UML类图就知道这是一个比较复杂的设计模式。

示例代码

针对上文中动物园的例子我们可以提取一下几种角色:

  1. 动物园:其实就相当于接口对象,里面存着很多场馆供访问者访问
  2. 场馆:就是元素对象,不同场馆票价不同
  3. 游客:对应这访问者,不同游客响应的折扣也不同

首先创建访问者: 根据上文我们之前到访问者抽象类只要提供访问的方法

public interface Tourist {
    void visit(PandaHouse pandaHouse);
    void visit(PenguinHouse penguinHouse);
}

访问者就是游客,游客可以访问不同的场馆,有些人可能异或为什么重载visit函数而不是只需要一个就可以,下文中具体说明

具体访问者

public class Student implements Tourist{
    @Override
    public void visit(PandaHouse pandaHouse) {
        System.out.println("学生观看熊猫馆的票价是:"+pandaHouse.getPandaHouseTicket() * 0.8);
    }

    @Override
    public void visit(PenguinHouse penguinHouse) {
        System.out.println("学生观看企鹅馆的票价是:"+penguinHouse.getPenguinHouseTicket() * 0.8);
    }
}
public class Serviceman implements Tourist{
    @Override
    public void visit(PandaHouse pandaHouse) {
        System.out.println("军人观看熊猫馆的票价是:"+pandaHouse.getPandaHouseTicket() * 0.5);
    }

    @Override
    public void visit(PenguinHouse penguinHouse) {
        System.out.println("军人观看企鹅馆的票价是:"+penguinHouse.getPenguinHouseTicket() * 0.5);
    }
}

接着上文说为什么visit重载这里主要是是因为每个场馆可能都有不同的功能,在实现visit方法的时候如果仅仅是个接口我们是不是要添加类型判断,多添加一种场馆就需要多加一个判断类似下面这种代码

public void visit(IHouse house) {
    if(house instanceof PenguinHouse){
        ...
    }else if(house instanceof PandaHouse){
        ...
    }
}

这样的话加一个场馆就要修改一下这个方法,如果我们吧visit方法职责单一化,是不是更好

再创建抽象元素 根据上文的UML图我们可以看出抽象角色主要就提供一个accept方法,用来统一访问者访问

public interface IHouse {
    void accept(Tourist people);
}

具体元素:

public class PandaHouse implements IHouse{
    @Override
    public void accept(Tourist people) {
        people.visit(this);
    }

    public int getPandaHouseTicket() {
        return 100;
    }
}
public class PenguinHouse implements IHouse{
    @Override
    public void accept(Tourist people) {
        people.visit(this);
    }

    public int getPenguinHouseTicket() {
        return 80;
    }
}

最后就是结构对象:动物园

public class Zoo {
    private List<IHouse> houseList = new ArrayList<IHouse>();
    {
        this.houseList.add(new PandaHouse());
        this.houseList.add(new PenguinHouse());
    }

    public void accept(Tourist tourist) {
        for (IHouse house : this.houseList) {
            house.accept(tourist);
        }
    }
}

houseList就是一个固定的结构(一般情况下动物园的场馆不会随便增加减少)

我们可以看一下这个例子的UML类图: 设计模式-访问者模式 和上文中的类图一样 执行结果如下:

public class Client {
   public static void main(String[] args) {
       Zoo zoo = new Zoo();
       zoo.accept(new Student());
       zoo.accept(new Serviceman());
   }
}

设计模式-访问者模式

访问者模式的优缺点

优点:

  1. 解耦了数据结构与数据操作,使得操作集合可以独立变化;
  2. 扩展性好:可以通过扩展访问者角色,实现对数据集的不同操作;
  3. 元素具体类型并非单一,访问者均可操作;
  4. 各角色职责分离,符合单一职责原则。

缺点:

  1. 无法增加元素类型:若系统数据结构对象易于变化,经常有新的数据对象增加进来,则访问者类必须增加对应元素类型的操作,违背了开闭原则;
转载自:https://juejin.cn/post/7200662591362121786
评论
请登录