设计模式——访问者模式(附代码示例)

一. 传统模式

以观众(分为男女两类)对歌手评价(分为成功、失败两类)为例

1. 传统方式解决

通过继承的方式实现男女分类,并直接在类中实现评价方法

2. 传统方式问题分析

        如果系统比较小,还是可以的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了ocp原则,不利于维护; 且扩展性不好,比如增加了新的人员类型,或者管理方法。故引出访问者模式。

二. 访问者模式

1. 概念

        访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题。

        访问者模式的基本工作原理是在被访问的类里面加一个对外提供接待访问者的接口。

        访问者模式主要应用场景是需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决。

2. 访问者模式原理类图


 

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

        ConcreteVisitor: 一个具体的访问值,实现每个有Visitor声明的操作,是每个操作实现的部分

        ObjectStructure:能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素

        Element:定义一个accept方法,接收一个访问者对象

        ConcreteElement:具体元素,实现了accept方法

3. 代码示例

类图

         Person抽象类,提供一个方法,让访问者可以访问

public abstract class Person {
    //提供一个方法,让访问者可以访问
    public abstract void accept(Action action);
}

         Man类,继承Person抽象类,使用到了双分派,即首先在客户端程序中,将具体状态作为参数传递到Man中(第一次分派);然后Man类调用作为参数的"具体方法"中方法getManResult, 同时将自己(this)作为参数传入(第二次分派)

         所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型

public class Man extends Person{
    @Override
    public void accept(Action action) {
        action.getManResult(this);
    }
}

         Woman类,继承Person抽象类,使用到了双分派

public class Woman extends Person{
    @Override
    public void accept(Action action) {
        action.getManResult(this);
    }
}

        Action抽象类,声明男女的评价方法

public abstract class Action {
    //男性评价
    public abstract void getManResult(Man man);
    //女性评价
    public abstract void getManResult(Woman woman);
}

         Success类,继承Action抽象类,具体实现男女对歌手成功的评价方法

public class Success extends Action{
    @Override
    public void getManResult(Man man) {
        System.out.println("男人给的评价是成功");
    }

    @Override
    public void getManResult(Woman woman) {
        System.out.println("女人给的评价是成功");
    }
}

         Fail类, 继承Action抽象类,具体实现男女对歌手失败的评价方法

public class Fail extends Action{
    @Override
    public void getManResult(Man man) {
        System.out.println("男人给的评价是失败");
    }

    @Override
    public void getManResult(Woman woman) {
        System.out.println("女人给的评价是失败");
    }
}

         ObjectStructure类,提供一个高层的接口,用来允许访问者访问元素

public class ObjectStructure {
    //维护一个集合
    private List<Person> persons = new LinkedList<>();
    
    //增加
    public void attach(Person person){
        persons.add(person);
    }
    //移除
    public void detach(Person person){
        persons.remove(person);
    }
    //显示测评情况
    public void display(Action action){
        for (Person person : persons) {
            person.accept(action);
        }
    }
}

         Client类,客户端

public class Client {
    public static void main(String[] args) {
        //创建ObjectStructure
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(new Man());
        objectStructure.attach(new Woman());


        //成功
        Success success = new Success();
        objectStructure.display(success);
        System.out.println("===============================");
        //失败
        Fail fail = new Fail();
        objectStructure.display(fail);
    }
}

 输出结果

4. 与传统方法相比

        以上述实例为例,假设我们要添加一个Wait的状态类,考察Man类和Woman类的反应,由于使用了双分派,只需增加一个Action子类即可在客户端调用即可,不需要改动任何其他类的代码,与传统方法相比扩展性更好,满足ocp原则

        Wait类,继承Action抽象类,具体实现男女对歌手待定的评价方法

public class Wait extends Action{
    @Override
    public void getManResult(Man man) {
        System.out.println("男人给的评价是待定");
    }

    @Override
    public void getManResult(Woman woman) {
        System.out.println("女人给的评价是待定");
    }
}

三. 总结 

1. 优点

        访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高

        访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统

2. 缺点

        具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样造成了具体元素变更比较困难

        违背了依赖倒转原则,访问者依赖的是具体元素,而不是抽象元素

3. 适用场景

        如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值