有一个数据结构有多种子数据结构聚合而成,须要在这些子数据结构分别进行不一样的操做,且有不少种不一样的操做类型。若是要在每一个数据结构里都分别定义对应的操做,会使得逻辑变得很复杂,并且当有新的操做类型时须要修改全部的类。编程
如图所示,咱们有两种 Element 类,为了在它们的持有者中实现两个操做 operate1 和 operate2,咱们须要在每一个 Element 里都实现操做的对应部分。若是这个时候咱们想要增长一种操做类型,那么咱们就必须修改每一个 Element 类。浏览器
假设咱们会常常变化操做的种类,那么咱们每次都要去修改全部的 Element 类, 这样会致使大量不相关的逻辑堆积在 Element 类中,最终致使代码变得难以维护。数据结构
为了解决这个问题,咱们能够尝试抽离变化的部分,在上述的例子中,变化的部分是具体的操做,那咱们就把操做部分的逻辑抽象出来。ide
咱们发现每一个操做都会遍历全部的 Element 对象,这个逻辑是不变的,变化的只是遍历时要作的事情,因此咱们把要作的事情定义成一个抽象层次,经过一个 Visitor 类来实现要作的事的逻辑,而本来的类自己只须要接收一个 Visitor 对象而后遍历全部成员并应用 visitor 对象来完成对成员对象的操做。这样咱们就将变化的部分从整个结构中抽离了出来,若是咱们须要增长一种新的操做,只须要在实现一个新的 Visitor 类就能够了。this
以上就是 Visitor 模式要处理的问题,经过一个观察者将实际的处理逻辑从数据结构类中抽离出来,这样每一个逻辑都完整的呈如今一个 Visitor 类中,而数据结构类也能够保持稳定的结构,不会由于加入过多的逻辑而变得难以维护。一个完整的 Visitor 模式的结构以下图所示:spa
和咱们上面的结构相比,实际的 Visitor 模式有一些变化:调用 Visitor 的逻辑并不放在顶层类中,而是在每一个 Element 类中定义了一个 accept
方法,顶层类只是依次调用 Element 的 accept
方法,而由 Element 类自己来调用 Visitor。为何要这样作呢?这就涉及到面向对象编程中多态相关的概念。code
面向对象编程一个最主要的概念就是类的继承,经过在类之间创建继承关系,咱们能够在须要一个父类声明的时候实际使用一个子类对象,若是这个子类对象复写了父类的方法,那么相同的调用在不一样的实际子类对象上就有了不一样的行为,这就是多态的概念。cdn
open class Source1
class Source2 : Source1()
open class Target1 {
open fun dispatch(source1: Source1) {
println("Dispatch Target1 from Source1")
}
open fun dispatch(source2: Source2) {
println("Dispatch Target1 from Source2")
}
}
class Target2 : Target1() {
override fun dispatch(source1: Source1) {
println("Dispatch Target2 from Source1")
}
override fun dispatch(source2: Source2) {
println("Dispatch Target2 from Source2")
}
}
复制代码
咱们实现了一个简单的继承关系,Target2 类继承了 Target1 类,这样若是咱们声明一个 Target1 的变量,并调用 dispatch 方法,经过给这个声明的变量赋值不一样的实际对象,就会有不同的行为:对象
var target: Target1 = Target1()
target.dispatch(Source1())
target = Target2()
target.dispatch(Source1())
复制代码
Output:blog
Dispatch Target1 from Source1
Dispatch Target2 from Source1
复制代码
咱们看到具体调用父类仍是子类的方法是在运行是动态决定的,这称为行为的动态分发。可是在通常的面向对象语言中,这种动态分发只适用于调用者,而不适用与参数:
val source: Source1 = Source2()
Target1().dispatch(source)
复制代码
Output:
Dispatch Target1 from Source1
复制代码
咱们看到对于传入的参数,系统并无在运行时经过实际的参数类型来决定应该调用哪一个方法,而只是根据声明时的参数类型来决定调用方法。
所以咱们说通常的面向对象语言都是单路分发的,即只有调用者有多态的行为而参数没有。如何实现调用者和参数均可以动态分发呢?咱们须要改变一下代码的结构:
open class Source1 {
open fun connect(target1: Target1) {
println("Dispatch Target1 from Source1")
}
open fun connect(target2: Target2) {
println("Dispatch Target2 from Source1")
}
}
class Source2 : Source1() {
override fun connect(target1: Target1) {
println("Dispatch Target1 from Source2")
}
override fun connect(target2: Target2) {
println("Dispatch Target2 from Source2")
}
}
open class Target1 {
open fun dispatch(source1: Source1) {
source1.connect(this)
}
}
class Target2 : Target1() {
override fun dispatch(source1: Source1) {
source1.connect(this)
}
}
复制代码
这样咱们至关于让参数也成为了调用者,经过两次的调用行为来模拟实现了二路分发。若是想实现多个参数的动态分发,能够按照这个思路继续扩展,让每一个参数都有机会成为一次调用者便可。实际的调用以下:
val source: Source1 = Source2()
Target1().dispatch(source)
复制代码
Output:
Dispatch Target1 from Source2
复制代码
咱们能够发现,这就是 Visitor 和咱们第一版方案的不一样之处。
Visitor 模式通常会用在编译器处理语法树或者 Web 浏览器解析 DOM 树的场景中。而若是代码须要实现多路分发的逻辑,也能够按照 visitor 模式的结构来实现。
by Orab.