让自己的内心藏着一条巨龙,既是一种苦刑,也是一种乐趣——雨果
今天跟着一个网站学了学访问者模式
简单来说,如果我们要在不改动现有逻辑的情况下对类进行增强,则可以使用访问者模式
真实世界类比
优秀的保险代理人总能为不同类型的团体提供不同的保单。
假如有这样一位非常希望赢得新客户的资深保险代理人。 他可以拜访街区中的每栋楼, 尝试向每个路人推销保险。 所以, 根据大楼内组织类型的不同, 他可以提供专门的保单:
如果建筑是居民楼, 他会推销医疗保险。
如果建筑是银行, 他会推销失窃保险。
如果建筑是咖啡厅, 他会推销火灾和洪水保险。
我们这里有多栋建筑,但我们不能在建筑类中写推销保险的代码,并且尽可能考虑未来拓展性
当设计完成后,此时此刻突然来了是送外卖的,我们现在再改动原有每个建筑的代码了,工作量就会太多。。。
我们尝试用访问者模式去完成这样一个案例:
建筑接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.ruben.vistor.example.Insurance;
public interface Building {
String getName();
void arrived();
void beVisited(Person person);
}
|
这个建筑接口中有三个函数,前面两个函数是我们需要具体执行的函数,第三个函数则是提供一个入口,让建筑能够被人访问
它的参数里是一个Person
类,对应人
我们现在先来实现这个Building
接口,写出居民楼、银行、咖啡厅的代码
居民楼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.ruben.vistor.example.Insurance;
public class ResidentialBuilding implements Building {
private String name;
public ResidentialBuilding(String name) { this.name = name; }
@Override public String getName() { return name; }
@Override public void arrived() { System.out.println("到达" + getName()); }
public void setName(String name) { this.name = name; }
@Override public void beVisited(Person person) { person.visit(this); } }
|
银行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.ruben.vistor.example.Insurance;
public class Bank implements Building {
private String name;
public Bank(String name) { this.name = name; }
@Override public String getName() { return name; }
@Override public void arrived() { System.out.println("到达" + getName()); }
public void setName(String name) { this.name = name; }
@Override public void beVisited(Person person) { person.visit(this); } }
|
咖啡厅
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.ruben.vistor.example.Insurance;
public class CoffeeShop implements Building {
private String name;
public CoffeeShop(String name) { this.name = name; }
@Override public String getName() { return name; }
@Override public void arrived() { System.out.println("到达" + getName()); }
public void setName(String name) { this.name = name; }
@Override public void beVisited(Person person) { person.visit(this); } }
|
然后是我们的Person
接口,其中有对应访问三个建筑的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.ruben.vistor.example.Insurance;
public interface Person {
void visit(ResidentialBuilding building);
void visit(Bank building);
void visit(CoffeeShop building); }
|
Person
对应的实现类
接下来就是我们的保险推销员了,写上具体增强逻辑
我们建筑本身没有能够被保险人员上门推销保险这一个功能,它只能被人访问,因此我们写完增强逻辑,对齐进行赋能,这样我们的保险推销员能够针对不同的建筑推销对应的保险
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package com.ruben.vistor.example.Insurance;
public class InsuranceSeller implements Person {
public void visit(Building building) { System.out.println("开始去" + building.getName() + "推销保险!"); building.beVisited(this); }
@Override public void visit(ResidentialBuilding building) { building.arrived(); System.out.println("开始推销医疗保险"); }
@Override public void visit(Bank building) { building.arrived(); System.out.println("开始推销失窃保险"); }
@Override public void visit(CoffeeShop building) { building.arrived(); System.out.println("开始推销火灾、洪水保险"); } }
|
这里第一个函数通过上面对Building
中第三个入口函数的调用,则调用到对应具体建筑的beVisited
方法,其中又调用了Person
中的visit
方法,注意这里调用的就不是我们下面第一个函数了,而是对应具体实现类参数的那一个visit
方法,如银行,则调用的是visit(Bank building)
最后则是调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.ruben.vistor.example.Insurance;
public class SellInsurance {
public static void main(String[] args) { InsuranceSeller insuranceSeller = new InsuranceSeller(); Building residentialBuilding = new ResidentialBuilding("碧桂园"); residentialBuilding.beVisited(insuranceSeller); Building bank = new Bank("农业银行"); bank.beVisited(insuranceSeller); Building coffeeShop = new CoffeeShop("瑞幸咖啡"); coffeeShop.beVisited(insuranceSeller); }
}
|
可以看到我这里成功让推销员对不同的建筑执行不同的逻辑,虽然是不同的子类,但我们传入的都是父类Building
,但保险推销员能根据不同的子类执行不同的逻辑了
此时如果我们加一个送外卖的业务,则可以只加一个外卖员类
然后让外卖员类中实现对各个建筑的逻辑代码,即可让外卖员自己根据传入的建筑执行不同的逻辑
执行结果
访问者模式优缺点
优点:
1.开闭原则。 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。
2.单一职责原则。 可将同一行为的不同版本移到同一个类中。
3.访问者对象可以在与各种对象交互时收集一些有用的信息。 当你想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。
缺点:
1.每次在元素层次结构中添加或移除一个类时, 你都要更新所有的访问者。(建了个奶茶店,你得在Person
、保险推销员和外卖员中都新增这个建筑)
2.在访问者同某个元素进行交互时, 它们可能没有访问元素私有成员变量和方法的必要权限。