访问者模式介绍
最复杂的设计模式,并且使用频率不高,《设计模式》的作者评价为:大多情况下,你不需要使用访问者模式,但是一旦需要使用它时,那就真的需要使用了。
访问者模式是一种将数据操作和数据结构分离的设计模式。(觉得太抽象,可以看下面的例子)。
访问者模式应用场景
1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
访问者模式应用实例
例如游客到景区游览 , 然后景区内分别由 大摆锤 , 海盗船等相关设施 , 游客若想"访问"这些设施 , 则需要通过售票处来购票才能进行访问 (例子而已,假设没有通票)
访问者模式UML类图
现在看这个UML类图可能还有些勉强 , 可以先往后看代码 , 稍后熟悉了访问者模式之后在回过头来看 会好些 .
类图角色介绍
- Visitor(访问者抽象类):接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素(具体被访问者类型)的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。
- ConcreteVisitor(具体访问者类):具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。此举违反了迪米特原则(具体元素对访问者公布)
- Element(被访问者):元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
- ElementA、ElementB(具体被访问者类):具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
- ObjectStructure:定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问。
访问者模式示例
需求背景 , 游客游览景区 , 景区内分别有 大摆锤 、海盗船、过山车这三样设施 , 若想访问者三者都需要通过售票处进行分别购票 , 才能访问 . 根据游客喜好不同 , 现有游客A 和游客B , 游客A想去 大摆锤 和 海盗船 , 而游客B想去 海盗船 和过山车 . 这时候就可以使用访问者模式来进行设计了 (举例而已, 有关游客多少 , 设施多少问题不做解释)
Faciility 类定义了游乐设施的基本信息及一个 accept 方法,accept 方法表示接受访问者的访问,由子类具体实现。
/**
* 设施基类(被访问者
*/
public abstract class Facility {
/**
* 设施名称
*/
protected String name;
public abstract void accept(TouristVisitor visitor);
public String getName() {
return name;
}
}
然后定义实现设施基础类的设施子类 , 分别定义几样设施
public class DaBaiChuei extends Facility{
public DaBaiChuei() {
this.name = "大摆锤";
}
@Override
public void accept(TouristVisitor visitor) {
visitor.visit(this);
}
}
public class GuoShanChe extends Facility {
public GuoShanChe() {
this.name = "过山车";
}
@Override
public void accept(TouristVisitor visitor) {
visitor.visit(this);
}
}
public class HaiDaoChuan extends Facility {
public HaiDaoChuan() {
this.name = "海盗船";
}
@Override
public void accept(TouristVisitor visitor) {
visitor.visit(this);
}
}
Visitor 是一个访问者接口,传入不同的实现类,可访问不同的数据。
Visitor 声明了三个 visit 方法,分别是对三种游乐设施的访问函数,具体代码如下:
public interface TouristVisitor {
/**
* 访问大摆锤
*
* @param daBaiChuei 被访问者
*/
void visit(DaBaiChuei daBaiChuei);
/**
* 访问海盗船
*
* @param haiDaoChuan 被访问者
*/
void visit(HaiDaoChuan haiDaoChuan);
/**
* 访问过山车
*
* @param guoShanChe 被访问者
*/
void visit(GuoShanChe guoShanChe);
}
接下来是实现了visitor的子类设施 , 分别模拟游客A , 和游客B
首先定义了一个 Visitor 接口,该接口有三个 visit 函数,参数分别是 三种游乐设施对象,也就是说对于不同游乐设施 的访问会调用两个不同的方法,以此达成区别对待、差异化处理。具体实现类为 TouristA 、TouristB类,具体代码如下:
public class TouristA implements TouristVisitor{
@Override
public void visit(DaBaiChuei daBaiChuei) {
System.out.println("游客A , 访问了 " + daBaiChuei.getName());
}
@Override
public void visit(HaiDaoChuan haiDaoChuan) {
System.out.println("游客A , 访问了 " + haiDaoChuan.getName());
}
@Override
public void visit(GuoShanChe guoShanChe) {
System.out.println("游客A , 访问了 " + guoShanChe.getName());
}
}
public class TouristB implements TouristVisitor {
@Override
public void visit(DaBaiChuei daBaiChuei) {
System.out.println("游客B , 访问了 " + daBaiChuei.getName());
}
@Override
public void visit(HaiDaoChuan haiDaoChuan) {
System.out.println("游客B , 访问了 " + haiDaoChuan.getName());
}
@Override
public void visit(GuoShanChe guoShanChe) {
System.out.println("游客B , 访问了 " + guoShanChe.getName());
}
}
demo
public class VisitorPatternDemo {
public static void main(String[] args) {
TouristVisitor visitorA = new TouristA();
TouristVisitor visitorB = new TouristB();
Facility daBaiChuei = new DaBaiChuei();
daBaiChuei.accept(visitorA);
Facility guoShanChe = new GuoShanChe();
guoShanChe.accept(visitorB);
Facility haiDaoChuan = new HaiDaoChuan();
haiDaoChuan.accept(visitorA);
haiDaoChuan.accept(visitorB);
}
}
总结
访问者模式最大的优点就是增加访问者非常容易,我们从代码中可以看到,如果要增加一个访问者,只要新实现一个 Visitor 接口的类,从而达到数据对象与数据操作相分离的效果。如果不实用访问者模式,而又不想对不同的元素进行不同的操作,那么必定需要使用 if-else 和类型转换,这使得代码难以升级维护。
访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
访问者(Visitor)模式的主要缺点如下。
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。