访问者模式
访问者模式
一、什么是访问者模式?
访问者模式(Visitor Design Patten):将作用于数据结构中的操作分离出来,形成单独的操作类,这样就可以在不改变数据结构的情况下,扩展出新的操作。
它将数据结构与数据处理分离开来,保持数据结构不变,而允许扩展新的处理。
二、访问者模式的应用场景是什么?
- 被处理的数据结构相对稳定
- 被处理的数据结构要求保持不变,尽量避免数据处理影响到数据结构
- 数据处理类型比较多,相对复杂,并且数据处理数量是可变化的,可扩展的
就比如说文件,文件类型虽然多,但是常用的也就那几种文件,如PPT、Word、Pdf等。
总的来说,文件数据类型相对来说比较稳定,不容易变动。
但是对文件的操作就比较频繁了,比如对文件内容搜索、分页、调整样式等,有各种各样的操作,是变化的。
这种情况下,使用访问者模式将文件和操作分离,很容易就能添加新的操作行为。
三、访问者模式的结构是怎么样的?
访问者模式中的登场角色:
- 访问者(Visitor):负责对数据结构中的每个具体元素(ConcreteElement)声明访问接口,表示访问该元素
- 具体的访问者(ConcreteVisitor):负责实现Visitor接口
- 元素(Element):表示被Visitor访问的对象,声明了接受Visitor访问的接口
- 具体元素(ConcreteElement):负责实现Element接口
- 对象结构(ObjectStructure):负责处理Element角色的集合
示例程序结构:
Viitor 和 Element:
1 | public interface Visitor { |
1 | public interface Element { |
Entry:
1 | public abstract class Entry implements Element { |
1 | public class File extends Entry { |
1 | public class Directory extends Entry { |
ListVisitor:
1 | public class ListVisitor implements Visitor { |
四、访问者模式有什么优缺点?
优点
- 可扩展性好。将数据与处理分离,方便增加新的处理
- 符合单一职责原则。每种操作都被封装成同一类的访问者,功能都比较单一
缺点:
- 可读性略差:访问者模式的代码结构是略微有点绕的,可读性和可理解性有点差
- 破坏依赖倒置原则。访问者依赖了具体的数据结构类型,而不是抽象数据结构
- 增加新的数据结构类麻烦。访问者是依赖了具体的数据结构的,增加一种新的数据结构,需要在访问者种增加新的处理接口
五、关于访问者的一些问题
5.1 访问者模式将数据与操作分离,是否违背面向对象设计原则?
- 从表面上看,分离数据和操作,是违背了面向对象设计原则的
- 从另一角度看,操作也可以认为是对象,访问者模式只是将数据对象和操作对象组合起来了
- 访问者结构中,数据是属于变化比较少的,而操作则是属于变化比较多的
- 访问者模式,将不变部分(数据)和可变部分(操作)分离,使用组合方式关联,是符合面向对象设计的
5.2 访问者模式的替代方案?
- 可使用工厂方法模式替代
- 每种操作类型,对应一个操作工厂类,为不同的数据类型生成不同的操作对象
- 比如文件Word、Pdf,抽取操作对应一个工厂ExtractFactory,为不同数据类型提供不同的实现WordExtractor、PdfExtractor
- 新增压缩操作Comoress,定义一个工厂CompressFactory,提供不同的操作实现WordCompressor、PdfCompressor
5.3 什么时候用访问者模式?什么时候用工厂方法模式?
具体的使用场景参考:
- 垂直扩展较多,即数据类型可变时,使用工厂模式比较合适
- 水平扩展较多,即操作类型可变时,使用访问者模式比较合适
- 垂直、水平扩展都比较多,即数据和操作都可变时,可使用模板模式 + 工厂模式/访问者模式
(1) 操作变化少,而数据类型经常变动,如添加新文件类型Txt、Excel等
- 使用访问者模式,就需要修改已有的每种操作类型,不符合开闭原则
- 使用工厂模式,只需要增加新的工厂类和操作类,无需修改原来的代码,符合开闭原则
(2) 数据类型基本不变,但是操作变化多,如为文件添加搜索、过滤、样式等操作
- 使用工厂模式,就需要为每种操作都添加工厂类和操作实现类,虽然符合开闭原则,但是类结构变得很复杂了,类数量也增多了
- 使用访问者模式,只需要添加新的操作类即可,符合开闭原则,而且可以避免增加很多类
(3) 数据类型和操作类型都可变,如文件可扩展Txt、Excel,操作也可扩展搜索、过滤等
- 在数据类型垂直扩展上,可以使用模板模式抽取公共的代码到父类中,避免重复代码
- 在操作类型水平扩展上,如果操作数量不多,类数量不多,可使用工厂模式实现
- 在操作类型水平扩展上,如果操作数量很多,可使用访问者模式实现,避免类数量过多