定义:
表示作用于某对象结构中的各个元素的操作。
可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
前提:
适用于数据结构(Element)相对稳定的系统,这样visitor中的方法就是稳定的。
不适用与数据结构易于变化的情况,不适用与此模式。如果总是有具体的Element加进来,visitor就需要增加新的方法,所有子类也需要增加,不符合开放-封闭原则。
例如:算法和数据结构(前提:有比较稳定的数据结构),需要易于变化的算法,使得算法操作的增删改变得容易。
优点:
把数据结构和作用于结构上的操作之间的耦合解开,使得操作集合可以相对自由地演化。即:把数据处理从数据结构分离出来。
增加新的操作很容易。增加新的操作意味着增加一个新的访问者(visitor),访问者模式将有关的行为集中到一个抽象对象中。
visitor可以单独开发,不必Element写在一起。visitor可以提高Element间的独立性。否则,每新增处理方法已扩充功能时,就必须修改ConcreteElementA、ConcreteElementB
缺点:
增加新的数据结构变得困难。
Tip:
很难找到数据结构不变化的情况,使用访问者模式的机会不多。
大多时候并不需要访问者模式,但当一旦需要访问者模式时,就针对需要它了。
访问者模式的能力、复杂性是把双刃剑,只有真正需要的时候,才可以考虑使用。
需要好好理解其适用性。
结构图:
Visitor调用Element;Element调用Visitor。双分派技术,处理实现与数据结构分离。
示例:
抽象类:关键在于人的分类(男人、女人)是稳定的。所以状态类中方法(男人反应、女人反应)个数是稳定的。抽象类人的抽象方法Accept(),用来获得状态对象。
具体状态类:
男人、女人类:
使用双分派技术:
第一次分派:将具体状态作为参数传递给“具体(男人、女人)类”。数据结构调用操作
第二次分派:具体(男人、女人)类调用“状态类”中的“男人、女人反应”,同时将自己(this:男人类、女人类)作为参数传递进去。操作使用数据结构中的数据
意味着:执行的操作取决于请求的种类(具体状态类)和接收者类型(男人、女人)。Accept()就是一个双分派的操作。
对象结构类:
针对不同状态,遍历男人、女人,得到不同反应
客户端:
优点:
增加状态(结婚……),查看男人、女人放映,只需增加一个状态子类,客户端调用查看即可,不用改动其他任何类的代码。
完美体现了“开放-封闭”原则。
对比:
客户端:
结果: