什么是访问者模式(Visitor Pattern)
概念
访问者模式(Visitor Pattern)属于行为型模式,定义:在类的内部结构不变的情况下,不同的访问者访问这个对象都会呈现出不同的处理方式。有一点点抽象,前一句话的意思是指在写好一个类之后,这个类就基本上不需要改动了(只要需求不改),这其实就是为了解决类结构不变但操作处理逻辑易变的问题,把对数据的操作都封装到访问者类中,我们只需要调用不同的访问者,而无需改变改变结构类,实现了。
又到了举例子的时间。来说说奶茶店会员,每个会员都是访问者,他们来到店里都是买奶茶,每个客户的操作都是一样的,下单、计算价钱、付钱,但不同级别的会员不同商品的打折力度是不一样的,如表格:
会员级别 | 一级会员 | 二级会员 | 三级会员 |
---|---|---|---|
珍珠奶茶 | 9.5折 | 9折 | 8.5折 |
水果茶 | 不打折 | 9.5折 | 9折 |
不同的会员(访问者)都有一套对应的处理逻辑。后面的代码实现部分用这个例子。
优点
- 提高了系统的复用性、可扩展性。
- 符合单一职责原则。
缺点
- 违反了开放封闭原则。如果想加新的元素,但凡增加了一个新的方法,我们得在每个访问者中新增访问方法,去修改原有的代码。
原则
“+”代表遵守,“-”代表不遵守或者不相关
原则 | 开放封闭 | 单一职责 | 迪米特 | 里氏替换 | 依赖倒置 | 接口隔离 | 合成复用 |
---|---|---|---|---|---|---|---|
- | + | - | - | - | - | - | |
适用场景
因为我在实际开发中也没有使用过这个设计模式,不过我知道“类结构很少发生改变,但操作处理逻辑易变”的情况,肯定要用这个设计模式,其他的适用场景,你看我的代码,能想到啥就啥哈哈。
如何实现
想要实现访问者模式,需要以下五样东西:
- 访问者抽象类/接口:给每一个具体元素定义一个visit()方法,传入参数的类型是具体元素的实现类。
- 具体访问者类:实现访问者抽象类/接口,实现该访问者访问不同元素要实现的处理逻辑。
- 元素抽象类/接口:定义元素的基本行为,需要包含一个accept()方法,入参是访问者。
- 具体元素类:实现元素抽象类/接口,accept()方法大多数都是直接调用访问者访问本身,还可以增加一些不同的处理逻辑。
- 对象结构类:负责连接访问者和元素对象。存储元素对象,并提供访问元素的方法,一般写在accrpt()方法中,这个方法的入参为访问者。
有点多了,我猜你可能也懒得全部看完,你只要记住访问者要实现visit方法,元素类和对象结构类要实现accept方法。
类图
这是我从其他网站复制的图。
代码
具体访问者:一级、二级、三级会员
具体元素:珍珠奶茶、水果茶
对象结构:奶茶店
类图
不是一般的复杂
会员
会员接口
/**
* 访问者接口
* 会员
* 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方法,还要传入奶茶类,晕😵。
总结
我在刚开始学习访问者模式的时候,我一直觉得它跟策略模式和状态模式特别像,它们都是把操作逻辑或者算法封装到一个类中,策略模式是把算法封装到类中,只要求这些类能够相互替换;状态模式是把不同状态的操作封装到类中,要求把状态和策略结合在一起;而访问者模式是把不同访问者对应的操作封装到类中,可以说每个类中装着一组的策略。
不过访问者模式的实现方式有点复杂,我在写例子的时候,突然想到,访问者模式是先定下访问不同元素的接口,具体的处理逻辑让具体访问者来实现,再加上对象结构类的逻辑基本上也不会变,貌似用模板方法模式也能够实现访问者模式,把奶茶店(对象结构类)写到抽象类中,再实现这个抽象类写成不同的会员类(具体访问者),而奶茶类(具体元素)就需要另外写了。思路没错,有没有小伙伴想试试?
——————————————————————————————
你知道的越多,不知道的就越多。
如果本文章内容有问题,请直接评论或者私信我。如果觉得我写得还不错的话,点个赞也是对我的支持哦
未经允许,不得转载!
近期评论