【设计模式】访问者模式,不愧是最复杂的设计模式之一!

👉设计模式目录

什么是访问者模式(Visitor Pattern)

概念

访问者模式(Visitor Pattern)属于行为型模式,定义:在类的内部结构不变的情况下,不同的访问者访问这个对象都会呈现出不同的处理方式。有一点点抽象,前一句话的意思是指在写好一个类之后,这个类就基本上不需要改动了(只要需求不改),这其实就是为了解决类结构不变但操作处理逻辑易变的问题,把对数据的操作都封装到访问者类中,我们只需要调用不同的访问者,而无需改变改变结构类,实现了。

本人输奶茶

又到了举例子的时间。来说说奶茶店会员,每个会员都是访问者,他们来到店里都是买奶茶,每个客户的操作都是一样的,下单、计算价钱、付钱,但不同级别的会员不同商品的打折力度是不一样的,如表格:

会员级别 一级会员 二级会员 三级会员
珍珠奶茶 9.5折 9折 8.5折
水果茶 不打折 9.5折 9折

不同的会员(访问者)都有一套对应的处理逻辑。后面的代码实现部分用这个例子。

优点

  1. 提高了系统的复用性、可扩展性。
  2. 符合单一职责原则。

缺点

  1. 违反了开放封闭原则。如果想加新的元素,但凡增加了一个新的方法,我们得在每个访问者中新增访问方法,去修改原有的代码。

原则

“+”代表遵守,“-”代表不遵守或者不相关

原则 开放封闭 单一职责 迪米特 里氏替换 依赖倒置 接口隔离 合成复用
- + - - - - -

适用场景

因为我在实际开发中也没有使用过这个设计模式,不过我知道“类结构很少发生改变,但操作处理逻辑易变”的情况,肯定要用这个设计模式,其他的适用场景,你看我的代码,能想到啥就啥哈哈。

如何实现

想要实现访问者模式,需要以下五样东西:

  1. 访问者抽象类/接口:给每一个具体元素定义一个visit()方法,传入参数的类型是具体元素的实现类。
  2. 具体访问者类:实现访问者抽象类/接口,实现该访问者访问不同元素要实现的处理逻辑。
  3. 元素抽象类/接口:定义元素的基本行为,需要包含一个accept()方法,入参是访问者。
  4. 具体元素类:实现元素抽象类/接口,accept()方法大多数都是直接调用访问者访问本身,还可以增加一些不同的处理逻辑。
  5. 对象结构类:负责连接访问者和元素对象。存储元素对象,并提供访问元素的方法,一般写在accrpt()方法中,这个方法的入参为访问者。

有点多了,我猜你可能也懒得全部看完,你只要记住访问者要实现visit方法,元素类和对象结构类要实现accept方法。

小黄鸡呆滞

类图

这是我从其他网站复制的图。

访问者(Visitor)模式的结构图

代码

具体访问者:一级、二级、三级会员

具体元素:珍珠奶茶、水果茶

对象结构:奶茶店

提醒喝奶茶小助手

类图

不是一般的复杂

image-20210616225236677

会员

会员接口
/**
 * 访问者接口
 * 会员
 * Created on 2021/6/16.
 *
 * @author xuxiaobai
 */
public interface Member {

    /**
     * 每个会员都要有访问珍珠奶茶和水果茶
     * 其实这里的奶茶价格我们都是可以直接访问的
     * 但这里要区分不同的会员,所以就得放到访问者类中去访问
     * 通过这样绕了一圈,就可以避免适用判断语句去判断当前会员是那个级别的
     */

    /**
     * 访问水果茶
     * @param tea 可以看到这里的入参是没有依赖接口的,是直接依赖于实现类的
     * @return
     */
    double visit(FruitMilkyTea tea);

    /**
     * 访问珍珠奶茶
     * @param tea
     * @return
     */
    double visit(PearlMilkyTea tea);

}
复制代码
一级会员
/**
 * 具体访问者
 * 一级会员
 *
 * Created on 2021/6/16.
 *
 * @author xuxiaobai
 */
public class LowMember implements Member{

    @Override
    public double visit(FruitMilkyTea tea) {
        return tea.getPrice()*0.95;
    }

    @Override
    public double visit(PearlMilkyTea tea) {
        return tea.getPrice();
    }
}
复制代码
二级会员
/**
 * 具体访问者
 * 二级会员
 * Created on 2021/6/16.
 *
 * @author xuxiaobai
 */
public class MiddleMember implements Member{
    @Override
    public double visit(FruitMilkyTea tea) {
        return tea.getPrice()*0.9;
    }

    @Override
    public double visit(PearlMilkyTea tea) {
        return tea.getPrice()*0.95;
    }
}
复制代码
三级会员
/**
 * 具体访问者
 * 三级会员
 * Created on 2021/6/16.
 *
 * @author xuxiaobai
 */
public class HighMember implements Member{
    @Override
    public double visit(FruitMilkyTea tea) {
        return tea.getPrice()*0.85;
    }

    @Override
    public double visit(PearlMilkyTea tea) {
        return tea.getPrice()*0.9;
    }
}
复制代码

奶茶

奶茶抽象类
/**
 * 元素抽象类
 * 奶茶
 * Created on 2021/6/16.
 *
 * @author xuxiaobai
 */
public abstract class MilkyTea {

    protected double price;

    public MilkyTea(double price){
        this.price=price;
    }


    /**
     * 每个元素都要接口访问者
     * @param member
     * @return
     */
    abstract double accept(Member member);

    public double getPrice() {
        return price;
    }
}
复制代码
珍珠奶茶
/**
 * 具体元素类
 * 珍珠奶茶
 * Created on 2021/6/16.
 *
 * @author xuxiaobai
 */
public class PearlMilkyTea extends MilkyTea {


    public PearlMilkyTea(double price) {
        super(price);
    }

    @Override
    public double accept(Member member) {
        //这里反过来,用会员(访问者)来调用奶茶(具体元素)
        return member.visit(this);
    }
}
复制代码
水果茶
/**
 * Created on 2021/6/16.
 *
 * @author xuxiaobai
 */
public class FruitMilkyTea extends MilkyTea {

    public FruitMilkyTea(double price) {
        super(price);
    }

    @Override
    public double accept(Member member) {
        return member.visit(this);
    }
}
复制代码

奶茶店

/**
 * 对象结构类
 * 奶茶店
 * Created on 2021/6/16.
 *
 * @author xuxiaobai
 */
public class MilkTeaShop {

    /**
     * 奶茶集合
     * 相当于客户下的奶茶订单
     */
    List<MilkyTea> milkyTeas = new ArrayList<>();

    public double accept(Member member) {
        //因为下面用了lambda,所以这里用了原子类
        AtomicReference<Double> sum = new AtomicReference<>((double) 0);
        milkyTeas.forEach((milkyTea) -> {
            //这里的意思就是每个奶茶对象去accept(会员),把获取的价格都加到sum中
            sum.updateAndGet(v -> new Double((double) (v + milkyTea.accept(member))));
        });
        /**
         * 这里可以算完后就清空掉集合
         * milkyTeas.clear();
         */
        return sum.get();
    }


    /**
     * 下单
     *
     * @param tea
     */
    public void add(MilkyTea tea) {
        milkyTeas.add(tea);
    }

    /**
     * 取消
     *
     * @param tea
     */
    public void remove(MilkyTea tea) {
        milkyTeas.remove(tea);
    }
}
复制代码

测试

/**
 * 访问者模式测试
 * Created on 2021/6/16.
 *
 * @author xuxiaobai
 */
public class VisitorTest {
    public static void main(String[] args) {
        //三种会员
        Member lowMember = new LowMember();
        Member middleMember = new MiddleMember();
        Member highMember = new HighMember();
        //奶茶店
        MilkTeaShop milkTeaShop = new MilkTeaShop();
        //点奶茶
        milkTeaShop.add(new PearlMilkyTea(10));
        milkTeaShop.add(new PearlMilkyTea(12));
        milkTeaShop.add(new PearlMilkyTea(16));
        milkTeaShop.add(new FruitMilkyTea(15));
        milkTeaShop.add(new FruitMilkyTea(17));
        System.out.println("同样的奶茶");
        System.out.println("一级会员的价格:" + milkTeaShop.accept(lowMember));
        System.out.println("二级会员的价格:" + milkTeaShop.accept(middleMember));
        System.out.println("三级会员的价格:" + milkTeaShop.accept(highMember));

        /**
         * 结果:
         * 同样的奶茶
         * 一级会员的价格:68.4
         * 二级会员的价格:64.89999999999999
         * 三级会员的价格:61.400000000000006
         *
         * 小数点有点多了哈哈,我就不去搞了
         */
    }
}
复制代码

虽然是把不同会员的处理逻辑抽离了出来,但这也太复杂了吧,又要调用奶茶类(访问者)的accept方法,还要传入会员(访问者),在奶茶类里面又要再调用会员的accpet方法,还要传入奶茶类,晕😵。

麻了麻中麻

总结

我在刚开始学习访问者模式的时候,我一直觉得它跟策略模式和状态模式特别像,它们都是把操作逻辑或者算法封装到一个类中,策略模式是把算法封装到类中,只要求这些类能够相互替换;状态模式是把不同状态的操作封装到类中,要求把状态和策略结合在一起;而访问者模式是把不同访问者对应的操作封装到类中,可以说每个类中装着一组的策略。

不过访问者模式的实现方式有点复杂,我在写例子的时候,突然想到,访问者模式是先定下访问不同元素的接口,具体的处理逻辑让具体访问者来实现,再加上对象结构类的逻辑基本上也不会变,貌似用模板方法模式也能够实现访问者模式,把奶茶店(对象结构类)写到抽象类中,再实现这个抽象类写成不同的会员类(具体访问者),而奶茶类(具体元素)就需要另外写了。思路没错,有没有小伙伴想试试?

小黄鸡滚球

——————————————————————————————

你知道的越多,不知道的就越多。

如果本文章内容有问题,请直接评论或者私信我。如果觉得我写得还不错的话,点个赞也是对我的支持哦

未经允许,不得转载!