设计模式-访问者模式

访问者模式是一种行为型设计模式,用于将数据结构与数据操作分离,使得在不改变数据结构的情况下添加新操作。它通过元素对象的accept方法接受访问者,然后在访问者内部通过visit方法执行特定操作,实现了对元素集合的灵活操作,避免了大量if-else或switch语句。访问者模式适用于数据结构稳定但操作多变的场景,具有良好的扩展性,但难以添加新的元素类型。
摘要由CSDN通过智能技术生成

访问者模式(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. 无法增加元素类型:若系统数据结构对象易于变化,经常有新的数据对象增加进来,则访问者类必须增加对应元素类型的操作,违背了开闭原则;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值