1.概述
访问者模式(Visitor Pattern)指的是在类的内部结构不变的情况下,不同的访问者访问这个对象都会呈现出不同的处理方式。它的主要作用时将数据结构与数据操作分离,将不同的算法与其所作用的对象进行分离。本文将详述访问者模式的原理及其使用方式。
2.原理及使用
2.1 原理
访问者UML类图如下所示:
类图中主要包含五个核心元素:抽象访问者接口(Visitor)、具体访问者类(ConcreteVisitorA、ConcreteVisitorB)、抽象元素接口(Element)、具体元素类(ConcreteElementA、ConcreteElementB)、对象结构类(ObjectStructure)。具体解释如下:
抽象访问者接口(Visitor):接口或抽象类,定义了对每个Element访问行为,逻辑上它内部的方法个数与元素的个数是一致的,正常情况下,Visitor内部元素的类型相对稳定,如果该接口需要经常变更,说明模式选择有问题,可能更适合策略模式或其他模式;
具体访问者类(ConcreteVisitorA、ConcreteVisitorB):实现抽象接口Visitor,内部实现访问者访问不同元素的处理逻辑;
抽象元素接口(Element):接口或抽象类,它定义了一个accept()方法,用于调用访问者本身,实现具体逻辑;
具体元素类(ConcreteElementA、ConcreteElementB):实现抽象元素(Element),accept()方法大多数直接调用Visitor,可增加具体的处理逻辑;
对象结构类(ObjectStructure):负责关联访问者和元素对象,存储元素对象,并提供访问元素的方法,一般会有accept()方法来解释访问者对象。
2.2 案例
现在有个超市,叫胖东西,顾客可以办理不同类型的会员卡(铂金会员、黄金会员、白银会员和普通会员),到了年底,不同类型的会员买年货(猪肉、牛奶等)会有不同的折扣(铂金会员5折,黄金会员7折,白银会员9折,普通会员不打折)。
根据访问者模式,画出UML类图如下:
代码如下:
public interface Goods {
double accept(Member member);
void display();
}
public class Milk implements Goods{
private String name="天真蓝水牛奶";
private double price;
private int number;
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Milk(double price, int number) {
this.price = price;
this.number = number;
}
@Override
public double accept(Member member) {
return member.visit(this);
}
@Override
public void display() {
System.out.println(name + ",价格:" + price + ",数量:" + number);
}
}
public class Pork implements Goods {
private String name = "进口黑猪肉";
private double price;
private int number;
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Pork(double price, int number) {
this.price = price;
this.number = number;
}
@Override
public double accept(Member member) {
return member.visit(this);
}
@Override
public void display() {
System.out.println(name + ",价格:" + price + ",数量:" + number);
}
}
public class ObjectStructure {
private static List<Goods> goods = new ArrayList<>();
//计算总价
public double accept(Member member) {
double sum = goods.stream().mapToDouble(goods1 -> goods1.accept(member)).sum();
return sum;
}
public void add(Goods good) {
goods.add(good);
}
public void remove(Goods good) {
goods.remove(good);
}
}
public interface Member {
//铂金会员折扣价
double visit(Pork pork);
//黄金会员折扣价
double visit(Milk milk);
}
public class Platinum implements Member {
@Override
public double visit(Pork pork) {
return 0.6 * pork.getPrice() * pork.getNumber();
}
@Override
public double visit(Milk milk) {
return 0.6 * milk.getPrice() * milk.getNumber();
}
}
public class Gold implements Member {
@Override
public double visit(Pork pork) {
return 0.7 * pork.getPrice() * pork.getNumber();
}
@Override
public double visit(Milk milk) {
return 0.7 * milk.getPrice() * milk.getNumber();
}
}
public class Silver implements Member{
@Override
public double visit(Pork pork) {
return 0.9 * pork.getPrice() * pork.getNumber();
}
@Override
public double visit(Milk milk) {
return 0.9 * milk.getPrice() * milk.getNumber();
}
}
public class Normal implements Member {
@Override
public double visit(Pork pork) {
return pork.getPrice() * pork.getNumber();
}
@Override
public double visit(Milk milk) {
return milk.getPrice() * milk.getNumber();
}
}
public class Client {
public static void main(String[] args) {
//铂金会员
Platinum platinum = new Platinum();
//黄金会员
Gold gold = new Gold();
//白银
Silver silver = new Silver();
//普通
Normal normal = new Normal();
ObjectStructure objectStructure = new ObjectStructure();
//买10箱牛奶
Goods milk = new Milk(129.0, 10);
//买10斤猪肉
Goods pork = new Pork(108.0, 10);
objectStructure.add(milk);
objectStructure.add(pork);
double accept = objectStructure.accept(platinum);
System.out.println("铂金会员,买10箱牛奶和10斤进口猪肉,总价为:" + accept);
double accept1 = objectStructure.accept(gold);
System.out.println("黄金会员,买10箱牛奶和10斤进口猪肉,总价为:" + accept1);
double accept2 = objectStructure.accept(silver);
System.out.println("白银会员,买10箱牛奶和10斤进口猪肉,总价为:" + accept2);
double accept3 = objectStructure.accept(normal);
System.out.println("普通会员,买10箱牛奶和10斤进口猪肉,总价为:" + accept3);
}
}
运行结果如下:
2.3 优点和缺点
2.3.1 优点
1.符合单一职责原则,让程序具有优秀的扩展性、灵活性;
2.访问者模式适用于数据结构相对稳定的系统,可以用来做过滤器与拦截器之类的功能。
2.3.2 缺点
1.违背迪米特法则,访问者关注了其它类的内部细节;
2.违背了依赖倒置原则,访问者访问的是具体元素,而不是抽象元素。
3.小结
1.访问者模式是设计模式中较为复杂的一种模式,主要适用于对象对应的类改变较少,同时需要对对象进行较多操作的场景;
2.在某种程度上,访问者模式与策略模式和状态模式有一定相似性,策略模式是抽象算法类,让子类去实现具体算法,根据条件进行算法替换;状态模式是将状态封装到对象中,将状态与行为结合;访问者则是将不同访问者对应的操作封装到类中,每个类中都有策略。
4.参考文献
1.《设计模式之禅》-秦小波著
2.《大话设计模式》-程杰著
3.https://www.bilibili.com/video/BV1G4411c7N4-尚硅谷设计模式
4.https://www.runoob.com/design-pattern/observer-pattern.html