今天我们来到行为关系的第四类。第一个要聊的就是访问者模式(Visitor Pattern)
。
这个模式号称所有设计模式中最复杂,最难理解的一种!
大家做好准备!我尽量说清楚些😂
简介
还是把这张概总图放这里。
访问者模式(Visitor Pattern),是一种将数据操作与数据结构分离的设计模式。
通常在OOP思想中,我们使用类来组织属性,以及对属性的操作,那么我们理所当然的将访问操作放到了类的内部。
但是,当后续我们想要进一步做些操作怎么办呢?在OOP的思想下,我们必须将这个类进行修改。可是,这在设计模式中是大忌,在设计模式中就要保证,对扩展开放,对修改关闭的开闭原则。
于是乎!前辈们开始考虑:是不是可以将访问操作独立出来变成一个新的类。
当我们需要增加访问操作的时候,直接增加新的类,原来的代码不需要任何的改变,如果可以这样做,那么我们的程序就是好的程序,因为可以扩展,符合开闭原则。
Duang~ 访问者模式由此而来。它实现了使用不同的访问方式对某些元素进行访问。
唉~这矛盾得让人头疼!
因此,我们才说这个模式是最复杂的一个。好在,它的使用频率不算很高。但是,一旦需要使用它的时候,你就得使用它。
举例解析
由于访问者模式不易懂,小二哥特意找了一个比较有趣的例子(感谢'伪资深码农'提供的这个示例)方便大家理解。
话说王二狗刚参加工作那会由于社会经验不足误入了一个大忽悠公司,公司老板不舍得花钱就给公司招了3个人,一个Hr,一个程序员,一个测试,但关键是老板总想追风口,啥都想做,一会社交,一会短视频。多次提出说人太少,申请加几个人,至少加个保洁阿姨啊,每天都自己打扫卫生,累屁了。每到此时老板就画大饼:你现在刚毕业正是要奋斗的时候,此时不奋斗什么时候奋斗?过两年公司上市了,你作为元老就财富自由拉...balabala
这个场景就很适合使用访问者模式:
大忽悠公司结构很稳定,老板舍不得花钱招人,总共就那么3个人,还是3种角色,即只有3个元素。大忽悠公司老板想法多,这就要求这3个人承担各种新技能,即不断的给元素增加新的算法。
第一步:构建Element毕竟改变的是元素(元素就是属性和行为方法构成的类。在本例中,就是公司的3个人)的算法,所以这里我们先构建元素。
社畜类只有一个accept方法,它需要一个访问者接口类型的参数
public interface CorporateSlave {
void accept(CorporateSlaveVisitor visitor);
}
ps:这是一个抽象接口:表示每个人都要接受外界的访问。也就是每个人每次可能接受不同的任务!
构建3个社畜的实现类:
程序员:
public class Programmer implements CorporateSlave {
private String name;
public Programmer(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(CorporateSlaveVisitor visitor) {
visitor.visit(this);
}
}
测试:
public class Tester implements CorporateSlave {
private String name;
public Tester(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(CorporateSlaveVisitor visitor) {
visitor.visit(this);
}
}
人力:
public class HumanSource implements CorporateSlave {
private String name;
public Tester(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(CorporateSlaveVisitor visitor) {
visitor.visit(this);
}
}
注意在element类里面将自己传递给visitor的visit()方法
@Override
public void accept(CorporateSlaveVisitor visitor) {
visitor.visit(this);
}
这里很关键:把类对象自己和要发生的操作类(任务类)对象 visitor 连接起来啦~
第二步:构建ObjectStructureBigHuYouCompany 类里面需要包含相对稳定的元素(大忽悠老板就招这3个人,再也不肯招人),而且要求可以对这些元素迭代访问。此处我们以集合存储3位员工。
public class BigHuYouCompany {
private List<CorporateSlave> employee= new ArrayList<>();
public BigHuYouCompany() {
employee.add(new Programmer("王二狗"));
employee.add(new HumanSource("上官无需"));
employee.add(new Tester("牛翠花"));
}
public void startProject(CorporateSlaveVisitor visitor){
for (CorporateSlave slave : employee) {
slave.accept(visitor);
}
}
}
第三步:构建VisitorVisitor 接口里面一般会存在与各元素对应的visit方法,例如此例我们有3个角色,所以这里就有3个方法。
public interface CorporateSlaveVisitor {
void visit(Programmer programmer);
void visit(HumanSource humanSource);
void visit(Tester tester);
}
🆗,来到我们的 Visitor 实现类了。也就是每一个实现类,就是一次不同的任务。
因为老板觉得社交是人类永恒的需求,所以开始想做社交App,他觉得他能成为微信第二。
这就相当于要为每一个元素(每一个人)定义一套新的算法:让程序员仿照微信开发设计app,让测试完成即时通信的测试,让人力发软文。
public class SocialApp implements CorporateSlaveVisitor {
@Override
public void visit(Programmer programmer) {
System.out.println(String.format("%s: 给你一个月,先仿照微信搞个类似的APP出来,要能语音能发红包,将来公司上市了少不了你的,好好干...",programmer.getName()));
}
@Override
public void visit(HumanSource humanSource) {
System.out.println(String.format("%s: 咱现在缺人,你暂时就充当了陪聊吧,在程序员开发APP期间,你去发发软文,积攒点粉丝...",humanSource.getName()));
}
@Override
public void visit(Tester tester) {
System.out.println(String.format("%s: 这是咱创业的第一炮,一定要打响,测试不能掉链子啊,不能让APP带伤上战场,以后给你多招点人,你就是领导了...",tester.getName()));
}
}
过了一段时间,老板又觉的短视频很火,又要做短视频,这就要求给每一员工增加一套新的算法。
public class LiveApp implements CorporateSlaveVisitor {
@Override
public void visit(Programmer programmer) {
System.out.println(String.format("%s: 最近小视频很火啊,咱能不能抄袭下抖音,搞他一炮,将来公司上市了,你的身价至少也是几千万,甚至上亿...",programmer.getName()));
}
@Override
public void visit(HumanSource humanSource) {
System.out.println(String.format("%s: 咱公司就数你长得靓,哪天化化妆,把你的事业线适当露一露,要是火了你在北京买房都不是梦...",humanSource.getName()));
}
@Override
public void visit(Tester tester) {
System.out.println(String.format("%s: 你也开个账户,边测试边直播,两不耽误...",tester.getName()));
}
}
再过段时间老板可能要开KTV,程序员王二狗可能要下海当鸭子,其他两位也需要解锁新技能...
客户端使用
public class VisitorClient {
public void startProject(){
BigHuYouCompany bigHuYou= new BigHuYouCompany();
//可以很轻松的更换Visitor,但是要求BigHuYouCompany的结构稳定
System.out.println("-----------------启动社交APP项目--------------------");
bigHuYou.startProject(new SocialApp());
System.out.println("-----------------启动短视频APP项目--------------------");
bigHuYou.startProject(new LiveApp());
}
}
输出:
--------------启动社交APP项目------------
王二狗: 给你一个月,先仿照微信搞个类似的APP出来,要能语音能发红包,将来公司上市了少不了你的,好好干...
上官无需: 咱现在缺人,你暂时就充当了陪聊吧,在程序员开发APP期间,你去发发软文,积攒点粉丝...
牛翠花: 这是咱创业的第一炮,一定要打响,测试不能掉链子啊,不能让APP带伤上战场,以后给你多招点人,你就是领导了...
-------------启动短视频APP项目------------
王二狗: 最近小视频很火啊,咱能不能抄袭下抖音,搞他一炮,将来公司上市了,你的身价至少也是几千万,甚至上亿...
上官无需: 咱公司就数你长得靓,哪天化化妆,把你的事业线适当露一露,要是火了你在北京买房都不是梦...
牛翠花: 你也开个账户,边测试边直播,两不耽误...
你看虽然大忽悠老板的需求变化这么快,但至始至终我们只是在增加新的Visitor实现类,而没有去修改任何一个Element类,这就很好的符合了开闭原则。
总结
在实践中,我们要根据具体情况来评估是否适合使用访问者模式。注意下面这几点:
- 如果一个对象结构不稳定决不可使用,不然在增删元素时改动将非常巨大
- 对象结构中的元素需要经常定义新的操作
- 对象结构中的元素要可以迭代访问
- Visitor里一般存在与元素个数相同的visit方法
- 元素通过accept方法通过this将自己传递给了Visitor
其次,访问者模式存在一个叫"伪动态双分派”的技术。这个还是比较难懂的,访问者模式之所以是最复杂的设计模式与其有很大的关系。
什么叫分派?就是根据对象的类型而对方法进行的选择,就是分派(Dispatch)。
如 slave.accept(visitor);
中accept方法的分派是由slave的运行时类型决定的。若slave是Programer就执行Programer的accept方法。若slave是Tester那么就执行Tester的accept方法。
最后,访问者模式的优缺点。
优点
- 各角色职责分离,符合单一职责原则 通过UML类图和上面的示例可以看出来,Visitor、ConcreteVisitor、Element 、ObjectStructure,职责单一,各司其责。
- 具有优秀的扩展性 如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。
- 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化,比较灵活 员工属性(数据结构)和CEO、CTO访问者(数据操作)的解耦。
缺点
- 具体元素对访问者公布细节,违反了迪米特原则 CEO、CTO需要调用具体员工的方法。
- 具体元素变更时导致修改成本大 变更员工属性时,多个访问者都要修改。
- 违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有以来抽象 访问者 visit 方法中,依赖了具体员工的具体方法。
ps:这个模式真的不好理解!大家看完后,再好好琢磨一下~