「补课」进行时:设计模式(18)——访问者模式
1. 前文汇总
2. 引言
访问者模式也可以说是所有设计模式中最难的一种设计模式了,当然我们平常也很少会用到它。设计模式的作者是这么评价访问者模式的:大多情况下,你并不需要使用访问者模式,但是一旦需要使用它时,那就真的需要使用了。
3. 一个简单的示例
又快到年底, CEO 和 CTO 开始评定员工一年的工作绩效,员工分为工程师和经理, CTO 关注工程师的代码量、经理的新产品数量; CEO 关注的是工程师的KPI和经理的KPI以及新产品数量。
由于 CEO 和 CTO 对于不同员工的关注点是不一样的,这就需要对不同员工类型进行不同的处理。访问者模式此时可以派上用场了。
首先定义一个员工基类 Staff :
public abstract class Staff {
public String name;
// 员工KPI
public int kpi;
public Staff(String name) {
this.name = name;
kpi = new Random().nextInt(10);
}
// 核心方法,接受Visitor的访问
abstract void accept(Visitor visitor);
}
Staff
类定义了员工基本信息及一个 accept()
方法, accept()
方法表示接受访问者的访问,由子类具体实现。
而 Visitor
是个接口,传入不同的实现类,可访问不同的数据。
下面是工程师和经理的具体实现类:
public class Engineer extends Staff {
public Engineer(String name) {
super(name);
}
@Override
void accept(Visitor visitor) {
visitor.visit(this);
}
// 工程师一年的代码数量
public int getCodeLines() {
return new Random().nextInt(10 * 10000);
}
}
public class Manager extends Staff {
public Manager(String name) {
super(name);
}
@Override
void accept(Visitor visitor) {
visitor.visit(this);
}
// 一年做的产品数量
public int getProducts() {
return new Random().nextInt(10);
}
}
工程师是代码数量,经理是产品数量,他们的职责不一样,也就是因为差异性,才使得访问模式能够发挥它的作用。
下面是 Visitor 接口的定义:
public interface Visitor {
// 访问工程师类型
void visit(Engineer engineer);
// 访问经理类型
void visit(Manager manager);
}
Visitor 声明了两个 visit 方法,分别是对工程师和经理对访问函数。
接下来定义两个具体的访问者: CEO 和 CTO 。
public class CEOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
System.out.println("工程师: " + engineer.name + ", KPI: " + engineer.kpi);
}
@Override
public void visit(Manager manager) {
System.out.println("经理: " + manager.name + ", KPI: " + manager.kpi + ", 新产品数量: " + manager.getProducts());
}
}
public class CTOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
System.out.println("工程师: " + engineer.name + ", 代码行数: " + engineer.getCodeLines());
}
@Override
public void visit(Manager manager) {
System.out.println("经理: " + manager.name + ", 产品数量: " + manager.getProducts());
}
}
接着是一个报表类,公司的 CEO 和 CTO 通过这个报表查看所有员工的业绩:
public class BusinessReport {
private List<Staff> mStaffs = new LinkedList<>();
public BusinessReport() {
mStaffs.add(new Manager("经理-A"));
mStaffs.add(new Engineer("工程师-A"));
mStaffs.add(new Engineer("工程师-B"));
mStaffs.add(new Manager("经理-B"));
mStaffs.add(new Engineer("工程师-C"));
}
/**
* 为访问者展示报表
* @param visitor 公司高层,如 CEO、CTO
*/
public void showReport(Visitor visitor) {
for (Staff staff : mStaffs) {
staff.accept(visitor);
}
}
}
最后是一个场景类:
public class Client {
public static void main(String[] args) {
// 构建报表
BusinessReport report = new BusinessReport();
System.out.println("=========== CEO看报表 ===========");
report.showReport(new CEOVisitor());
System.out.println("=========== CTO看报表 ===========");
report.showReport(new CTOVisitor());
}
}
执行结果如下:
=========== CEO看报表 ===========
经理: 经理-A, KPI: 7, 新产品数量: 8
工程师: 工程师-A, KPI: 6
工程师: 工程师-B, KPI: 3
经理: 经理-B, KPI: 4, 新产品数量: 4
工程师: 工程师-C, KPI: 2
=========== CTO看报表 ===========
经理: 经理-A, 产品数量: 6
工程师: 工程师-A, 代码行数: 61280
工程师: 工程师-B, 代码行数: 10353
经理: 经理-B, 产品数量: 5
工程师: 工程师-C, 代码行数: 65827
4. 访问者模式
4.1 定义
访问者模式(Visitor Pattern) 是一个相对简单的模式, 其定义如下:
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. (封装一些作用于某种数据结构中的各元素的操作, 它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。 )
4.2 通用类图
- Visitor 抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素。
- ConcreteVisitor 具体访问者:它影响访问者访问到一个类后该怎么干, 要做什么事情。
- Element 抽象元素:接口或者抽象类,声明接受哪一类访问者访问。
- ConcreteElement 具体元素:实现方法。
- ObjectStruture 结构对象:元素产生者,一般容纳在多个不同类、不同接口的容器。
4.3 通用代码
抽象元素:
public abstract class Element {
// 定义业务逻辑
abstract void doSomething();
// 定义允许访问角色
abstract void accept(IVisitor visitor);
}
具体元素:
public class ConcreteElement1 extends Element{
@Override
void doSomething() {
}
@Override
void accept(IVisitor visitor) {
visitor.visit(this);
}
}
public class ConcreteElement2 extends Element{
@Override
void doSomething() {
}
@Override
void accept(IVisitor visitor) {
visitor.visit(this);
}
}
抽象访问者:
public interface IVisitor {
void visit(ConcreteElement1 ele1);
void visit(ConcreteElement2 ele2);
}
具体访问者:
public class Visitor implements IVisitor{
@Override
public void visit(ConcreteElement1 ele1) {
ele1.doSomething();
}
@Override
public void visit(ConcreteElement2 ele2) {
ele2.doSomething();
}
}
结构对象:
public class ObjectStruture {
public static Element createElement() {
Random random = new Random();
if (random.nextInt(100) > 50) {
return new ConcreteElement1();
} else {
return new ConcreteElement2();
}
}
}
场景类:
public class Client {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Element e1 = ObjectStruture.createElement();
e1.accept(new Visitor());
}
}
}
4.4 优点
-
各角色职责分离,符合单一职责原则。
通过UML类图和上面的示例可以看出来,Visitor、ConcreteVisitor、Element 、ObjectStructure,职责单一,各司其责。
-
具有优秀的扩展性。
如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。
-
使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
员工属性(数据结构)和CEO、CTO访问者(数据操作)的解耦。
-
灵活性。
4.5 缺点
-
具体元素对访问者公布细节,违反了迪米特原则。
CEO、CTO需要调用具体员工的方法。
-
具体元素变更时导致修改成本大。
变更员工属性时,多个访问者都要修改。
-
违反了依赖倒置原则,为了达到「区别对待」而依赖了具体类,没有用来抽象。
访问者 visit 方法中,依赖了具体员工的具体方法。
作者:极客挖掘机
定期发表作者的思考:技术、产品、运营、自我提升等。
本文版权归作者极客挖掘机和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果您觉得作者的文章对您有帮助,就来作者个人小站逛逛吧:极客挖掘机