访问者模式(Visitor Pattern),是在 GoF 23 种设计模式中定义了的行为型模式。据《大话设计模式》中说算是最复杂也是最难以理解的一种模式了。 访问者模式 是一种将数据操作与数据结构分离的设计模式,它可以算是 23 中设计模式中最复杂的一个,但它的使用频率并不是很高,大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是需要使用它了。 ~ 本篇文章内容包括:关于访问者模式、访问者模式 Demo
文章目录- 一、关于访问者模式
- 1、关于访问者模式
- 2、关于访问者模式的构成
- 3、关于访问者模式的UML
- 4、关于访问者模式的适用场景
- 5、关于访问者模式的优缺点
- 1、关于访问者模式
- 2、关于访问者模式的构成
- 3、关于访问者模式的UML
- 4、关于访问者模式的适用场景
- 5、关于访问者模式的优缺点
- 二、访问者模式 Demo
- 1、Demo 设计
- 2、Demo 实现
- 3、Demo 测试
一、关于访问者模式
1、关于访问者模式
访问者模式(Visitor Pattern),是在 GoF 23 种设计模式中定义了的行为型模式。据《大话设计模式》中说算是最复杂也是最难以理解的一种模式了。
访问者模式 是一种将数据操作与数据结构分离的设计模式,它可以算是 23 中设计模式中最复杂的一个,但它的使用频率并不是很高,大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是需要使用它了。
访问者模式 的基本想法是,软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个 accept 方法用来接受访问者对象的访问。访问者是一个接口,它拥有一个 visit 方法,这个方法对访问到的对象结构中不同类型的元素做出不同的处理。在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施 accept 方法,在每一个元素的 accept 方法中会调用访问者的 visit 方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。
访问者模式 适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式
2、关于访问者模式的构成
访问者模式主要包含以下 5 种角色:
- 抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
- 具体访问者(ConcreteVisitor)角色: 具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
- 抽象元素(Element)角色:定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
- 具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
- 对象结构(ObjectStructure)角色: 对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。
3、关于访问者模式的UML
4、关于访问者模式的适用场景
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
5、关于访问者模式的优缺点
# 访问者模式的优点
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
# 访问者模式的缺点
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
二、访问者模式 Demo
1、Demo 设计
艺术公司利用“铜”可以设计出铜像,利用“纸”可以画出图画;造币公司利用“铜”可以印出铜币,利用“纸”可以印出纸币。对“铜”和“纸”这两种元素,两个公司的处理方法不同,所以该实例用访问者模式来实现比较适合。
- 首先,定义一个公司(Company)接口,它是抽象访问者,提供了两个根据纸(Paper)或铜(Cuprum)这两种元素创建作品的方法;再定义艺术公司(ArtCompany)类和造币公司(Mint)类,它们是具体访问者,实现了父接口的方法。
- 然后,定义一个材料(Material)接口,它是抽象元素,提供了 accept(Company visitor)方法来接受访问者(Company)对象访问;再定义纸(Paper)类和铜(Cuprum)类,它们是具体元素类,实现了父接口中的方法。
- 最后,定义一个材料集(SetMaterial)类,它是对象结构角色,拥有保存所有元素的容器 List,并提供让访问者对象遍历容器中的所有元素的 accept(Company visitor)方法。
2、Demo 实现
# Company 抽象访问者(Visitor)角色
代码语言:javascript复制public interface Company {
/**
* 操作Page元素
*
* @param element
* @return
*/
String create(Paper element);
/**
* 操作Cuprum元素
*
* @param element
* @return
*/
String create(Cuprum element);
}
# ArtCompany/MintCompany 具体访问者(ConcreteVisitor)角色
代码语言:javascript复制public class ArtCompany implements Company {
@Override
public String create(Paper element) {
// 艺术公司利用Paper元素操作
return "打印广告";
}
@Override
public String create(Cuprum element) {
// 艺术公司利用Cuprum元素制造铜像
return "孔子铜像";
}
}
public class MintCompany implements Company {
@Override
public String create(Paper element) {
// 造币公司利用Paper元素造纸币
return "纸币";
}
@Override
public String create(Cuprum element) {
// 造币公司利用Cuprum元素造铜币
return "铜币";
}
}
# Material 抽象元素(Element)角色
代码语言:javascript复制public interface Material {
/**
* 给指定访问者提供访问当前元素(就是this)的方法
*
* @param visitor 指定的访问者
* @return 访问当前元素返回的结果
*/
String accept(Company visitor);
}
# Paper/Cuprum 具体元素(ConcreteElement)角色
代码语言:javascript复制public class Paper implements Material {
@Override
public String accept(Company visitor) {
// 让指定访问者visitor访问当前Paper元素
return visitor.create(this);
}
}
public class Cuprum implements Material {
@Override
public String accept(Company visitor) {
// 让指定访问者visitor访问当前Cuprum元素
return visitor.create(this);
}
}
# MaterialSet 对象结构(ObjectStructure)角色
代码语言:javascript复制public class MaterialSet {
/**
* 存储材料元素的集合
*/
private List<Material> list = new ArrayList<>();
/**
* 让指定访问者访问list集合中的所有元素
*
* @param visitor 指定的访问者
* @return 批量访问的结果
*/
public String accept(Company visitor) {
// 获取集合的迭代器
Iterator<Material> iterator = list.iterator();
// 遍历集合,让集合中的所有材料元素都被当前访问者所访问
String result = "";
while (iterator.hasNext()) {
result = iterator.next().accept(visitor) " ";
}
// 返回某公司的作品集
return result;
}
/**
* 添加元素到材料集合中
*
* @param element 待添加的元素
*/
public void add(Material element) {
list.add(element);
}
/**
* 删除集合中的指定元素
*
* @param element 待删除的元素
*/
public void remove(Material element) {
list.remove(element);
}
}
3、Demo 测试
代码语言:javascript复制public class Client {
public static void main(String[] args) {
// 创建材料元素集合
MaterialSet ms = new MaterialSet();
// 向集合中添加元素
ms.add(new Paper());
// 向集合中添加元素
ms.add(new Cuprum());
// 创建具体的访问者,让该访问者来访问对象结构中的所有元素
Company artCompany = new ArtCompany();
System.out.println(ms.accept(artCompany));
System.out.println("==========================");
// 创建具体的访问者,让该访问者来访问对象结构中的所有元素
Company mintCompany = new MintCompany();
System.out.println(ms.accept(mintCompany));
}
}