访问者模式 定义
封装一些作用于某种数据结构中的各个元素的操作,他可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
访问者模式使用场景
1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
UML类图
角色介绍
(1) visitor:接口或者抽象类-它定义了对每个元素访问的行为,他的参数就是访问的元素,理论上讲,该类的方法数量和元素数量是一致的。因此访问者模式需要要求元素的类簇是要稳定的,如果经常添加、删除元素类、必然会频繁的修改访问者类。如果出现这种情况,说明场景不适合访问者模式。
(2)ConcreateVisitor :具体的访问者,他需要给每个元素类访问时产生的具体行为。
(3)Element:元素类,他要定义一个接受访问的方法(accept),其意义是指每个元素都应该可以被访问者访问。
(4)ConcreateElement:具体的元素类,他提供具体的访问方法实现。一般由访问者提供
(5)ObjectStructure:定义对象结构,对象结构是一个抽像表述,它内部管理了元素类,并且可以迭代这些对象共访问者访问。
访问者模式简单实例
在年终的时候,公司都会对员工的业绩进行考核。但是不同领域的管理人员对员工的评定是不一样的。为了简单说明问题,我们把员工分成工程师和经理,评定员工分成CEO 和CTO 。我们假定CTO只关注工程师代码量,经理新产品数量,而CEO关注的是工程师的KPI和经理的KPI以及产品的数量。
从中可以看出CEO 和CTO的关注点是不一样的,这就要对不同的员工进行不同的处理,这时访问者模式就排上用场了。
/**
* 基类 - 员工
* @author Zero
*/
public abstract class Staff {
public String name;
public int kpi;
public Staff(String name) {
super();
this.name = name;
this.kpi = new Random().nextInt(10);
}
/**
* 接受上级访问(查岗)
*
* @param visitor
*/
public abstract void accept(Visitor visitor);
}
员工提供了一个accept方法 提供访问者访问。具体访问逻辑由子类实现。
/**
* 员工 - 工程师
* @author Zero
*/
public class Engineer extends Staff {
public Engineer(String name) {
super(name);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* 工程师一年的代码量
* @return
*/
public int getCodeLines() {
return new Random().nextInt(10000 * 10);
}
}
/**
* 员工 - 经理
* @author Zero
*/
public class Manager extends Staff {
int products;
public Manager(String name) {
super(name);
products = new Random().nextInt(10);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* 一年内做的产品数量
*
* @return
*/
public int getProducts() {
return products;
}
}
工程师和经理分别提供不同的访问业绩方法。并把访问逻辑委托到访问者去实现。
/**
* 统计报表 - 业务报告
* @author Zero
*/
public class BusinessReport {
List<Staff> mStaffs = new LinkedList<>();
public BusinessReport() {
mStaffs.add(new Manager("经理 - 王"));
mStaffs.add(new Manager("经理 - 赵"));
mStaffs.add(new Engineer("工程师 - 李"));
mStaffs.add(new Engineer("工程师 - 张"));
mStaffs.add(new Engineer("工程师 - 钱"));
mStaffs.add(new Engineer("工程师 - 马"));
}
/**
* 为公司领导展示报表
*/
public void showReprot(Visitor visitor) {
for (Staff staff : mStaffs) {
staff.accept(visitor);
}
}
}
下面看看访问者的方法定义
public interface Visitor {
/**
* 访问工程师类型
* @param engineer
*/
public void visit(Engineer engineer) ;
/**
* 访问经理类型
* @param manager
*/
public void visit(Manager manager) ;
}
提供了一个重载的访问方法,目的就是区别对待 、差异化处理不同的元素。
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.products);
}
}
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.products);
}
}
如果我们需要使用一个访问方法 的方式去做,我们的代码大致是这样子的:
public void visit(Staff staff) {
if(staff instanceof Engineer) {
//做一些操作
}else {
//做一些操作
}
}
如果类型再多了,那将会出现大量的if - else 这样代码逻辑就会越来越复杂。可以看出使用访问者模式,使代码简洁、扩展性更强了。
客户端代码:
public class Client {
public static void main(String[] args) {
BusinessReport report = new BusinessReport();
System.out.println("============ 给CEO看的报表 ============");
report.showReprot(new CEOVisitor());
System.out.println("============ 给CTO看的报表 ============");
report.showReprot(new CTOVisitor());
}
}
UML类图
优缺点评定
优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。
缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
网友评论