1.访问者模式的简介
将作用于某种数据结构中的各[元素的操作]分离出来[封装成独立的类],使其在[不改变数据结构]的前提下可以[添加作用于这些元素的新的操作],为数据结构中的每个元素提供多种访问方式;
它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
2.访问者模式的角色
- 抽象访问者(Visitor)-[访客]角色:定义一个访问具体元素的接口,为每个具体元素类(东家)对应一个访问操作(看人-不同的东家,不同的操作) ,该操作中的[参数类型]标识了被访问的具体元素(参数类型为东家的声明)。
- 抽象元素(Element)-[东家]角色:声明一个包含接受操作 accept(访客) 的接口,访客对象作为 accept() 方法的参数。
- 具体访问者(ConcreteVisitor)-[访客]角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问每一个元素时该做什么(多态)。
- 具体元素(ConcreteElement)-[东家]角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
- 对象结构(Object Structure)-[存放多个东家-可迭代(访客为入参)]角色:是一个包含元素角色的容器(多个东家的集合),提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
3.访问者模式的简单示例
一个公司的员工上班期间不能穿拖鞋,但是回到家可以;在这里公司、家都是两个不容易变动的部门,员工作为一个访客在不同的环境下有不同的行为;
抽象访问者(Visitor)-[访客]角色
/**
* 抽象访问者(Visitor)-[访客]角色
*/
public interface A1_Visitor {
/**
* 定义访客的行为:在家里穿的鞋子
*/
void putonShoe(B1_Element_Home element);
/**
* 定义访客的行为:在公司穿的鞋子
*/
void putonShoe(B2_Element_Company element);
}
抽象元素(Element)-[东家]角色 : 这里指公司、家
/**
* 抽象元素(Element)-[东家]角色 : 这里指公司、家
*/
public abstract class A2_Element {
//声明接受哪一类访问者访问
public abstract void accept(A1_Visitor visitor);
}
具体元素(ConcreteElement)-[东家]角色 : 这里指 家
/**
* 具体元素(ConcreteElement)-[东家]角色 : 这里指 家
*/
public class B1_Element_Home extends A2_Element{
@Override
public void accept(A1_Visitor visitor) {
visitor.putonShoe(this);
}
}
具体元素(ConcreteElement)-[东家]角色 : 这里指 公司
/**
* 具体元素(ConcreteElement)-[东家]角色 : 这里指 公司
*/
public class B2_Element_Company extends A2_Element{
@Override
public void accept(A1_Visitor visitor) {
visitor.putonShoe(this);
}
}
具体访问者(ConcreteVisitor)-[访客]角色 : 这里指员工在家里、公司 的表现
/**
* 具体访问者(ConcreteVisitor)-[访客]角色 : 这里指员工在家里、公司 的表现
*/
public class C1_Visitor_Staff implements A1_Visitor {
@Override
public void putonShoe(B1_Element_Home element) {
System.out.println("员工在家里:开心的穿着拖鞋");
}
@Override
public void putonShoe(B2_Element_Company element) {
System.out.println("员工在单位:优雅的穿着皮鞋");
}
}
对象结构(Object Structure)-[存放多个东家-可迭代(访客为入参)]角色 : 这里存放 公司、家对象
/**
* 对象结构(Object Structure)-[存放多个东家-可迭代(访客为入参)]角色 : 这里存放 公司、家对象
*/
public class D1_Structure {
private List<A2_Element> elements = new ArrayList<>();
public void elementPut(A2_Element element){
elements.add(element);
}
public void elementRemove(A2_Element element){
elements.remove(element);
}
public void accept(A1_Visitor visitor){
for(A2_Element item : elements){
item.accept(visitor);
}
}
}
客户端调用
/**
* 客户端调用
*/
public class E1_Client {
public static void main(String[] args) {
//对象结构(Object Structure)-[存放多个东家-可迭代(访客为入参)]角色
D1_Structure structure = new D1_Structure();
A2_Element home = new B1_Element_Home();
A2_Element company = new B2_Element_Company();
structure.elementPut(home);
structure.elementPut(company);
//具体访问者(ConcreteVisitor)-[访客]角色
A1_Visitor visitor = new C1_Visitor_Staff();
//将对数据的操作与数据结构进行分离
structure.accept(visitor);
}
}
调用结果:
员工在家里:开心的穿着拖鞋
员工在单位:优雅的穿着皮鞋
4.访问者模式的优点
- 扩展性好 : 能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能(集合添加的都是接口或抽象类)。
- 复用性好 : 可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好 : 访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则 : 访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
5.访问者模式的缺点
- 增加新的元素类很困难(东家类)。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则” (增加一个东家,就需要在访客类中增加对该东家的处理方式)。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
6.访问者模式的应用场景
- 1、一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
- 2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor模式使得你可以将相关的操作集中起来定义在一个类中。
- 3、当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。