设计之禅——访问者模式
引言
访问者模式是遵循单一职责原则,将行为和属性分离的一种设计模式,它可以在不改变元素结构的前提下定义元素新的操作。类比到现实当中就像博物馆,它是固定不变的,其中有各种各样的展品,而我们就是访客,可以是参观、可以是打扫、也可以是管理。同样访客模式正是具有了这样一个特点。
正文
访客模式含有五大角色,分别是:
- 抽象元素(AbstractElement):抽象类或接口,定义一个accept方法,供访问者访问
- 具体元素(ConcreteElement):访问者待访问的对象,实现accept方法
- 抽象访问者(AbstractVisitor):抽象类或接口,定义visit方法,访问不同的元素
- 具体访问者(ConcreteVisitor):实现visit方法,如何访问元素
- 结构对象(ObjectStruct):组合元素类,交给访问者
通过这五个角色我们便能将变化的部分(行为)独立到访问者类中,这样增加新的行为就不会影响到元素类了。那该如何实现呢?下面我以一个汽车零件的例子来说明。
Coding
首相是元素族,即汽车零件:
public abstract class Components {
protected String brand;
public Components(String brand) {
this.brand = brand;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public abstract void accept(IVisitor visitor);
}
public class Engine extends Components {
private String power;
public Engine(String brand, String power) {
super(brand);
this.power = power;
}
public String getPower() {
return power;
}
public void setPower(String power) {
this.power = power;
}
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
public class CarBody extends Components {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public CarBody(String brand, String color) {
super(brand);
this.color = color;
}
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
这里只是为了说明,所以只列出了车身和引擎部件,它们公用的是品牌属性,而其分别含有颜色和功率属性。
public interface IVisitor {
void visit(CarBody carBody);
void visit(Engine engine);
}
public class BugattiModifiedVisitor implements IVisitor {
@Override
public void visit(CarBody carBody) {
System.out.println("改装前品牌:" + carBody.getBrand() + ",改装前车身颜色:" + carBody.getColor());
carBody.setBrand("布加迪威航");
carBody.setColor("black");
System.out.println("改装后品牌:" + carBody.getBrand() + ",改装后车身颜色:" + carBody.getColor());
}
@Override
public void visit(Engine engine) {
System.out.println("改装前品牌:" + engine.getBrand() + ",改装前功率:" + engine.getPower());
engine.setPower("500");
engine.setBrand("布加迪威航");
System.out.println("改装后品牌:" + engine.getBrand() + ",改装后功率:" + engine.getPower());
}
}
public class BenzModifiedVisitor implements IVisitor {
@Override
public void visit(CarBody carBody) {
System.out.println("改装前品牌:" + carBody.getBrand() + ",改装前车身颜色:" + carBody.getColor());
carBody.setBrand("奔驰");
carBody.setColor("blue");
System.out.println("改装后品牌:" + carBody.getBrand() + ",改装后车身颜色:" + carBody.getColor());
}
@Override
public void visit(Engine engine) {
System.out.println("改装前品牌:" + engine.getBrand() + ",改装前功率:" + engine.getPower());
engine.setPower("300");
engine.setBrand("奔驰");
System.out.println("改装后品牌:" + engine.getBrand() + ",改装后功率:" + engine.getPower());
}
}
这里IVisitor接口中我定义了两个visit方法,分别访问了CarBody和Engine,同时我实现了两个访问者:布加迪威航改装者和奔驰改装者,分别将原始车辆部件替换为布加迪威航和奔驰的部件。你需要思考的是为什么不定义一个visit方法传入一个Components类,这样增加新元素类不就不用修改接口了?
public class ObjectStructure {
public static List<Components> getComponents() {
String brand = "大众";
CarBody carBody = new CarBody(brand, "红色");
Engine engine = new Engine(brand, "100");
List<Components> list = new ArrayList<>();
list.add(carBody);
list.add(engine);
return list;
}
}
最后是结构对象类,这里只是简单的组合各元素类到一个集合中,方便访问者依次访问。 至此,就实现了一个标准的访问者模式,我们增加新的行为(比如增加一个其它品牌的访问者或者是修理工访问者)对元素类不会有任何的影响,只需要实现IVisitor接口即可;不过,刚刚你也注意到,在访问者接口中我们定义了两个方法,分别访问不同的元素,这样,一旦增加新的元素类,我们就不得不修改访问者接口,同时每个具体的访问者也不得不实现新增的行为,这简直是灾难,所以这就是访问者模式的显著缺点,它仅适合元素类固定不变的情况,所以大多也都是在重构项目时更适合使用(因为此时更能够判断元素类的变化情况)。当然这个缺点我们也可以完善,在访问者接口方法中传入抽象的元素类型而不是具体的元素类型即可解决,但是这样相当于所有的元素类都具有相同的操作,违背了访问者模式的初衷(单一职责)。或许我们可以通过向下强制转型并使用if else或者是责任链模式来解决,但这样势必会导致我们的类结构变得异常复杂,因此我们在考虑使用访问者模式的时候一定要慎重。
总结
访问者模式可以帮助我么将变化的行为独立到元素的外部,但和其它设计模式一样都有其优点和缺点,在实际开发中,我们不要过度设计,想用而用,而要真正的考虑各种模式的使用范围。
- 算法模板——线段树9(区间加+区间求和+区间方和)
- 1709: [Usaco2007 Oct]Super Paintball超级弹珠
- 2015: [Usaco2010 Feb]Chocolate Giving
- 2060: [Usaco2010 Nov]Visiting Cows 拜访奶牛
- 2020: [Usaco2010 Jan]Buying Feed, II
- 2102: [Usaco2010 Dec]The Trough Game
- 洛谷P3707 [SDOI2017]相关分析(线段树)
- Java计数器之CountDownLatch、CyclicBarrier、Semaphore
- 再看最短路算法 1 —— 单源最短路
- 3402: [Usaco2009 Open]Hide and Seek 捉迷藏
- 1084: [SCOI2005]最大子矩阵
- 关于一般的并查集求根操作的一组对照研究
- vue计算属性详解——小白速会
- 【技巧】Java工程中的Debug信息分级输出接口及部署模式
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux查看系统配置常用命令详解
- LNMP下提示File not found问题的解决方法
- Linux 配置SSH免密登录 “ssh-keygen”的基本用法
- 详解Ubuntu 16.04 pycharm设置桌面快捷启动方式
- Linux 7.4上安装配置Oracle 11.2.0.4图文教程
- linux磁盘管理软RAID的实现原理分析和方法分享
- Centos7下Samba服务器配置(实战)
- Linux系统中创建SSH服务器别名的两种方法
- Linux下卸载MySQL8.0版本的操作方法
- Linux服务器上安装Python3的两种方式
- Centos7安装ElasticSearch 6.4.1入门教程详解
- Windows 和 Linux 上Redis的安装守护进程配置方法
- 在Linux系统上安装Spring boot应用的教程详解
- 使用openssl 生成免费证书的方法步骤
- linux cd的含义以及用法