美文网首页
设计模式之--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