走穿java23种设计模式–21访问者模式详解
访问者模式的目的是封装一些施加于某些数据结构元素之上的操作,即使这些操作需要修改,接收这个操作的数据结构却可以保持不变。
一.访问者模式的现实场景
卢康身体很不舒服,他感觉自己有点感冒。为了更快好起来,他去了XX医院。到了医院,他先去挂号,然后跟医生讲自己哪里不舒服,医生给他看了一下,让他去验血。等他验血报告出来后,医生又给他开了一些药,让他按照药方去拿药即可。卢康拿着单子去交钱,然后吧交钱的单子給药店,拿了药回家。、
上面这个场景跟我们现实生活中看病是差不多的吧。
场景中,卢康去医院看病,拿单子,交钱和拿药这个过程,对于单子进行访问操作的收银员和药剂师可以看作是两个访问者,类似设计模式中的访问者。
怎么理解呢,其实上面现实场景中两个访问者,药剂师和收银员。我们去病历单,生成病单,就相当与一种封装,不管谁来看病这个封装过程都是类似的,都生成一个病历单,而这时药剂师能根据这个封装的结果来获取到里面的信息。同样的,收银员也是,通过不同病历单上的数据解析封装得到不同的账单,收银员通过访问账单就可以得到一些信息。上面不同病人的病情不同,生成的病历单不同,但是药剂师都能读懂这个病历单;不同病人的账单不同,收银员也是能够计算出具体收费情况。
二.访问者模式(Visitor Pattern)的定义
封装一些作用于某种数据结构的各种元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作。
三.访问者模式的类图
四.访问者模式的五个角色
1.抽象访问者(Visitor)角色
该角色声明一个或多个访问操作,定义访问者可以访问哪些元素。
2.具体访问者(Concrete Visitor)角色
该角色实现抽象访问者角色中的各个访问者操作。
3.抽象元素(Element)角色
该角色声明一个接收操作,接收一个访问者的对象。
4.具体元素(Concrete Element)角色
该角色实现抽象元素角色中的接收操作
5.结构元素(Object Structure)角色
该角色有一下责任:
可以遍历结构中所有的元素。
如果需要,该角色提供一个高层次的接口让访问者对象可以访问每一个元素,也可以设计一个复合对象或者一个集合,如List或Set。
五.访问者模式的优缺点
访问者模式中的优点
1.访问者模式使得增加形的操作变得更加容易,只增加新的访问者类即可。
2.访问者模式将有关的行为收集到一个访问者对象中,而不是分散到多个元素中。
3.访问者模式可以跨过几个类的等级机构访问属于不同的等级结构的成员类。
4.累积状态。每一个单独的访问者对象都集中了相关的行为,从而分散到很多的元素对象中,易于系统的维护。
访问者模式的缺点
1.增加新的元素类变得困难。每增加一个形的元素类就意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。
2.破坏封装。访问者模式要求访问者对象访问每一个元素对象并调用没有给元素对象的操作,这隐含了一个对所有元素对象的要求,即访问者对象必须暴露一些自己的操作和内部状态,否则访问者的访问就没有意义。
3.违背了依赖倒置原则。访问者模式依赖的是具体的元素,而不是抽象的元素,这破坏了依赖倒置的原则,特别是在面向对象的编程中,访问者对象抛弃了对接口的依赖,而直接依赖实现类,扩展比较难。
六.访问者模式的使用场景
1.一个对象结构包含很多类对象,它们有不同的接口,当对这些对象实施依赖于具体类的操作时,即在迭代器模式不能胜任的场景下,可以采用访问者模式。
2. 需要对一个对象结构中的对象进行很多不同并且不相关的操作,避免操作污染类。
3.业务规则要求遍历多个不同的对象这本身也是访问者模式的出发点。
迭代器模式只能访问同类或者接口的数据,而访问者是对迭代器模式的扩充,他可以遍历不同的对象,执行不同的操作。
七.访问者模式的示例
这里使用上面的现实场景做代码示例。
示例的类图
示例的代码
1.抽象药品类Medicine
package p21_vistor;
/**
* Medicine抽象类,
* 储备药品信息,对应访问者模式中的抽象元素角色.
*/
public abstract class Medicine {
private String name;//药名
private Double price;//价格
public Medicine(String name, Double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
//接收访问者
public abstract void accept(IVisitor visitor);
}
2.抽象访问者IVisitor
package p21_vistor;
/**
* 抽象访问者
* 这里使用的是java的多态,传入对应的对象就会调用对应的方法
*/
public interface IVisitor {
//对药品进行操作
void doVisitor(MedicineA a);
void doVisitor(MedicineB b);
}
3.药品A类MedicineA
package p21_vistor;
/**
* MedicineA类.继承Medicine类
*/
public class MedicineA extends Medicine{
public MedicineA(String name, Double price) {
super(name, price);
}
//对药品进行操作
@Override
public void accept(IVisitor visitor) {
visitor.doVisitor(this);
}
}
4.药品B类MedicineB
package p21_vistor;
/**
* MedicineB类.继承Medicine类
*/
public class MedicineB extends Medicine {
public MedicineB(String name, Double price) {
super(name, price);
}
//对药品进行操作
@Override
public void accept(IVisitor visitor) {
visitor.doVisitor(this);
}
}
5.收银员类Cashier
package p21_vistor;
/**
*收营员,负责給药品收款
*/
public class Cashier implements IVisitor {
@Override
public void doVisitor(MedicineA a) {
System.out.println("收银员給药品:"+a.getName()+" ,收款 :"+a.getPrice());
}
@Override
public void doVisitor(MedicineB b) {
System.out.println("收银员給药品:"+b.getName()+" ,收款 :"+b.getPrice());
}
}
6.药剂师类Pharmacists
package p21_vistor;
/**
* 药剂师负责取药品
*/
public class Pharmacists implements IVisitor{
@Override
public void doVisitor(MedicineA a) {
System.out.println("药剂师拿药品:"+a.getName()+"給取药人");
}
@Override
public void doVisitor(MedicineB b ) {
System.out.println("药剂师拿药品:"+b.getName()+"給取药人");
}
}
7.药单类MedicaineList
package p21_vistor;
/**
* 取药单子的类
*/
public class MedicineList {
private MedicineA a;
private MedicineB b;
//添加药品和对应的价格
public MedicineList() {
this.a = new MedicineA("板蓝根", 8.8);
this.b = new MedicineB("白加黑", 11.8);
}
//对药品操作的人,就是药剂师和收银员啦,他们都是要分别对这两个商品进行不同的操作
public void accept(IVisitor visitor){
this.a.accept(visitor);
this.b.accept(visitor);
}
}
8.测试类
package p21_vistor;
/**
* 测试类
*/
public class MediicineDemo {
public static void main(String[] args){
//药单
MedicineList list=new MedicineList();
//收银员
Cashier cashier=new Cashier();
//药剂师
Pharmacists pharmacists=new Pharmacists();
//先是付钱
list.accept(cashier);
System.out.println("========================");
//接着去取药
list.accept(pharmacists);
}
}
程序运行结果;
访问者模式模式就为大家介绍到这里。
大家如果对其他设计模式有兴趣可以看看我之前的博客:
20备忘录模式:https://blog.csdn.net/wenzhi20102321/article/details/80468151
19观察者模式:https://blog.csdn.net/wenzhi20102321/article/details/80330335
18中介者模式:https://blog.csdn.net/wenzhi20102321/article/details/79394668
17迭代器模式:http://blog.csdn.net/wenzhi20102321/article/details/79343423
16策略模式:http://blog.csdn.net/wenzhi20102321/article/details/79336521
15责任链模式:http://blog.csdn.net/wenzhi20102321/article/details/79333899
14命令模式:http://blog.csdn.net/wenzhi20102321/article/details/79323404
13模板方法模式:http://blog.csdn.net/wenzhi20102321/article/details/79284870
12享元模式:http://blog.csdn.net/wenzhi20102321/article/details/78724677
11外观模式:http://blog.csdn.net/wenzhi20102321/article/details/78639087
10桥梁模式:http://blog.csdn.net/wenzhi20102321/article/details/78566532
9组合模式:http://blog.csdn.net/wenzhi20102321/article/details/78463190
8适配器模式:http://blog.csdn.net/wenzhi20102321/article/details/78389326
7装饰模式:http://blog.csdn.net/wenzhi20102321/article/details/78336273
6代理模式:http://blog.csdn.net/wenzhi20102321/article/details/78209493
创建型模式详解:http://blog.csdn.net/wenzhi20102321/article/details/78175558
5原型模式:http://blog.csdn.net/wenzhi20102321/article/details/78167984
4建造者模式:http://blog.csdn.net/wenzhi20102321/article/details/78163855
3抽象工厂模式:http://blog.csdn.net/wenzhi20102321/article/details/78153437
2工厂方法模式:http://blog.csdn.net/wenzhi20102321/article/details/78129065
可以仔细对比一下工厂方法模式和抽象工厂模式,看看概念,看看类图,看看代码,就会明白了。
1单例模式:http://blog.csdn.net/wenzhi20102321/article/details/77882203
java 23种设计模式介绍:http://blog.csdn.net/wenzhi20102321/article/details/54601909