美文网首页
设计模式之--Visitor

设计模式之--Visitor

作者: 027f63d16800 | 来源:发表于2017-12-05 15:24 被阅读881次

单派

OOP的编程语言中,比如java或者kotlin,都支持单派,即根据引用的实际类型去执行对应的方法,这也是多态的基础
例如:

interface Element{
    fun foo()
}
class ElementA:Element{
    override fun foo() {
        println("this is element A")
    }
}

class ElementB:Element{
    override fun foo() {
        println("this is element B")
    }
}

接口Element有两个子类,当我们面向接口编程时,总能执行实际类型对应的方法:

fun main(vararg args:String){
    var ele:Element = ElementA()
    ele.foo()  ==>this is element A
    ele = ElementB()
    ele.foo()  ==>this is element B
}

然而,当类型出现在参数列表时,比如:

fun invokeFoo(ele:ElementA){
    ele.foo()
}

fun main(vararg args:String){
    var ele:Element= ElementA()
    invokeFoo(base)  //无法通过编译
}

我们会发现,上面的语句无法通过编译,因为java不支持根据参数的实际类型进行派遣,在编译时,无法通过类型的静态检查

修改上面的两个子类,分别为他们添加方法:

class ElementA:Element{
    override fun foo() {
        println("this is element A")
    }
    fun methodA()=Unit
}

class ElementB:Element{
    override fun foo() {
        println("this is element B")
    }
    fun methodB()=Unit
}

现在,我们需要实现一个方法,接收一个Element引用,并根据它的实际类型去分别调用methodA或者methodB,我们很容易就写出:

fun invokeFoo(ele:Element){
    when (ele){
        is ElementA -> ele.methodA()
        is ElementB -> ele.methodB()
        else -> throw IllegalArgumentException("ele 类型参数不匹配")
    }
}

我们去判断ele的实际类型,然后分别为不同类型执行不同的处理逻辑,比如为ElementA调用methodA

然而,当Element新增了子类,我们都要来修改这个方法,这个方法将会变得越来越臃肿。

为了能够简化这个方法,我们可能会想为每个子类都声明一个函数,从而构成一组重载函数,比如:

fun invokeFoo(ele:ElementA){
    ele.methodA()
}
fun invokeFoo(ele:ElementB){
    ele.methodB()
}

这样,每个Element都有自己的一个处理函数,每次Element添加了子类型,只要新增加一个对应的函数就可以了,而且每次执行而不需要额外的判断逻辑
但是根据上面的分析,java并不支持根据参数的实际类型进行派遣。

使用Visitor

Visitor模式使用的是双重派遣,基于上面的问题,我们引入新的类结构:

图片来自网上
我们引入了新的类结构Visitor,在Visitor中,我们为每个Element的子类都声明了一个visit函数,从而构成了一系列的重载函数。
对于每个Element的子类中,我们都需要显示的重写accept函数:
override fun accept(v: Visitor) {
        v.visit(this)
    }

在一个类中使用this引用,静态编译时,编译器就会认为该引用的类型为当前方法所在类
当我们通过ele.accept(visitor)来执行ele的某个方法时,首先会根据ele的实际类型进行派遣,进入accept方法内之后,会根据visitor的实际类型和this的类型再次进行派遣,决定执行visitor的哪个重载函数。

模拟代码:

interface Element {
    fun accept(v: Visitor)

}

class ElementA : Element {

    fun methodA() = println("visit element a")
    override fun accept(v: Visitor) {
        v.visit(this)
    }
}

class ElementB : Element {
    fun methodB() = println("visit element b")
    override fun accept(v: Visitor) {
        v.visit(this)
    }
}

interface Visitor {
    fun visit(ele: ElementA)
    fun visit(ele: ElementB)
}

class VisitorA : Visitor {

    override fun visit(ele: ElementA) {
        ele.methodA()
    }

    override fun visit(ele: ElementB) {
        ele.methodB()
    }
}

fun main(vararg args: String) {
    var ele: Element = ElementA()
    ele.accept(VisitorA())

}

通过使用visitor模式,当我们新增一个Element的子类时,我们只需要在Visitor的子类中为他新增一个visit方法,而无需更改其他函数的逻辑,而且每个visit方法都只关注于具体的处理逻辑。使用Visitor模式更利于后期的维护。

相关文章

网友评论

      本文标题:设计模式之--Visitor

      本文链接:https://www.haomeiwen.com/subject/foyomxtx.html