设计模式——访问者模式
什么是访问者模式
访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
可以对定义这么理解:有这么一个操作,它是作用于一些元素之上的,而这些元素属于某一个对象结构。同时这个操作是在不改变各元素类的前提下,在这个前提下定义新操作是访问者模式精髓中的精髓。
主要解决:稳定的数据结构和易变的操作耦合问题。就是把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。
本质:预留通路,回调实现。它的实现主要就是通过预先定义好调用的通路,在被访问的对象上定义accept方法,在访问者的对象上定义visit方法;然后在调用真正发生的时候,通过两次分发的技术,利用预先定义好的通路,回调到访问者具体的实现上。
访问者模式的结构
Visitor抽象访问者接口:它定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变(不能改变的意思是说,如果元素类的个数经常改变,则说明不适合使用访问者模式)。
ConcreteVisitor具体访问者角色:它需要给出对每一个元素类访问时所产生的具体行为。
Element抽象节点(元素)角色:它定义了一个接受访问者(accept)的方法,其意义是指,每一个元素都要可以被访问者访问。
ConcreteElement具体节点(元素)角色:它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
ObjectStructure结构对象角色:这个便是定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。
访问者模式的使用场景
通常在以下情况可以考虑使用访问者(Visitor)模式。
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
- 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
访问者模式的优缺点
访问者(Visitor)模式是一种对象行为型模式,其主要优点如下:
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
访问者(Visitor)模式的主要缺点如下:
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
访问者模式的实现
抽象访问者角色:为每一个具体节点都准备了一个访问操作。
public interface Visitor {
void visit(FreeCourse freeCourse);
void visit(CodingCourse codingCourse);
}
具体访问者
public class VisitorImpl implements Visitor {
@Override
public void visit(FreeCourse freeCourse) {
//访问免费课程,打印所有的免费课程名称
System.out.println("免费课程:"+freeCourse.getName());
}
@Override
public void visit(CodingCourse codingCourse) {
//访问收费课程,打印所有的收费课程名称及价格
System.out.println("收费课程:"+codingCourse.getName()+",价格:"+codingCourse.getPrice());
}
}
抽象节点类
public abstract class Course {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void accept(Visitor visitor);
}
具体节点类
public class CodingCourse extends Course {
private int price;
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class FreeCourse extends Course {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
结构对象角色类
public class ObjectStructure {
private List<Course> courseList = new ArrayList<>();
public void accept(Visitor visitor){
courseList.forEach(course -> course.accept(visitor));
}
public void add(Course course){
courseList.add(course);
}
public void remove(Course course){
courseList.remove(course);
}
}
客户端代码
public class Test {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
FreeCourse freeCourse = new FreeCourse();
freeCourse.setName("Spring课程");
CodingCourse codingCourse = new CodingCourse();
codingCourse.setName("SpringBoot课程");
codingCourse.setPrice(399);
objectStructure.add(freeCourse);
objectStructure.add(codingCourse);
objectStructure.accept(new VisitorImpl());
}
}
访问者(Visitor)模式的扩展
访问者(Visitor)模式是使用频率较高的一种设计模式,它常常同以下两种设计模式联用。
- 1、与迭代器模式联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。
- 2、访问者(Visitor)模式同组合模式联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式,其结构图如图 4 所示。