访问者模式
定义
访问者模式(Visitor Pattern)是面向对象编程及设计模式中的一种,它允许你在不改变一个类的层次结构的前提下,定义一个新的操作于该类层次的各个类的所有对象的算法。
解释一下:现在有一个封装好的数据结构,需要访问该数据结构的所有或部分数据,不同的数据访问可能有细微的区别,该设计模式分离了访问所有元素的操作与具体元素的操作
类图
角色说明
- 对象结构(Object Structure):已经封装好的数据结构,包含一系列Element对象
- 抽象元素(Element):被访问的数据元素接口,定义了一个接受访问者的方法( accept )
- 具体元素(ConcreteElement): 具体数据元素实现类,提供接受访问方法的具体实现。
- 抽象访问者(Visitor):接口或者抽象类,定义了一系列操作方法,定义访问操作方法
- 具体访问者(ConcreteVisitor):访问者接口的实现类,定义具体访问方法
- 客户端(Client): 使用已定义好的数据结构,并访问该数据结构(间接访问所有元素)
实践
公司的一个项目的后台管理需要使用压缩包导入某些东西,压缩包中的每一级目录或元素都对应于数据库中的某些信息,毕竟压缩包是个树型结构,当时想到的做法是组合模式,但实现后发现很鸡肋,因为结构中每个元素都需要访问数据库,导致创建元素时不得不将相关DAO传过去并保存起来(因该还有更简单的做法),但是元素中包含那么多DAO总归是很奇怪的。
后来阅读了《软件设计:从专业到卓越》 这本书,在关注点分离中,作者举了个例子,就是打印文件夹中所有文件夹及文件的名字,作者经过一系列重构后得到了一个结论,遍历文件夹是一个关注点,遍历到的元素如何操作是另一个关注点,可以将两者分离开来
于是乎,我在第二次重构代码时就采用了这个方法
代码
class FileTree<T> {
private FileTreeNode<T> root;
public void addFile(List<String> filePath, T desc) {
filePath.add(0, root.getFileName());
root.addFile(filePath, desc);
}
public void foreach(Consumer<FileTreeNode<T>> consumer) {
for (Map.Entry<String, FileTreeNode<T>> entry : root.getChildren().entrySet()) {
entry.getValue().foreach(consumer);
}
}
}
public class FileTreeNode<T> {
private String fileName;
/**
* 文件描述,可能是流,ZipEntry,File,String,byte[]
*/
private T desc;
private Map<String, FileTreeNode<T>> children;
private FileTreeNode<T> parent;
public void addFile(List<String> filePath, T desc) {
// 添加节点
}
public void foreach(Consumer<FileTreeNode<T>> consumer) {
consumer.accept(this);
for (Map.Entry<String, FileTreeNode<T>> entry : children.entrySet()) {
entry.getValue().foreach(consumer);
}
}
}
这个从某种程度上来说属于访问者模式,这里的访问者是java语言自带的函数式编程中的Consumer接口,利用关注点分离法,将具体的FileTree遍历方式与访问方式分离,从而使得foreach函数内聚性更强,更加灵活
学到这里突然发现有时候在学设计模式的时候也没必要钻牛角尖,大体知道常见设计模式的适用范围即可,这种非技术上的成长更加可贵,当你试图去了解一个设计模式,他里面蕴藏的逻辑无形之中对代码有了不可逆转的改变