【问题标题】:Best way to implement visitor pattern in Kotlin在 Kotlin 中实现访问者模式的最佳方法
【发布时间】:2015-11-09 04:46:06
【问题描述】:

在 Kotlin 中实现 visitor pattern 是否有任何技巧或常用方法?任何对初学者来说可能不明显但会导致代码更简洁或有条理的东西。

为澄清而编辑:我有一个 AST,其中包含许多 (~30) 类型的节点。目前,每个类都实现了自己的 print() 方法,我想将其分解为单独的 Printer 类。有了访问者模式,添加其他 AST 遍历类会更简洁,其中会有几个。

【问题讨论】:

  • 您是否要求某人代表您为 Kotlin 编写示例访问者模式?这个问题很宽泛、开放式,很可能应该被标记为结束。
  • 不,我只是在问如何以我仍在学习的语言惯用地实现特定模式——仅此而已。
  • 发布您对它的最佳猜测,让我们帮助您调整它。可能您只需将 lambda 传递给将遍历的节点并在访问节点时回调 lambda。从那里开始,发布代码,然后我们可以从那里提供帮助。
  • 好的,鉴于我对您的类层次结构以及哪些问题发挥作用一无所知,因此我采用了一般方法来回答您的问题。见下文。

标签: design-patterns kotlin


【解决方案1】:

阅读this answer 了解 Java 8,它所说的一切也适用于 Kotlin:

对 Java 语言的添加不会使每个旧概念都过时。事实上,访问者模式非常擅长支持添加新操作。

这适用于 Kotlin。与 Java 8 一样,它具有 LambdasSAM conversionsinterfaces that allow default implementations

一个变化是,如果您要进行类实例类型检查,而不是对每个 instanceof 检查使用大的 if 语句,请在 Kotlin 中使用 when expression

在不同答案的同一个 Stackoverflow 页面上,它谈到了正在使用的 Lambda,并在 Java 中显示 if 语句来决定调用哪个 lambda。而不是他们的Java sample

if (animal instanceof Cat) {
    catAction.accept((Cat) animal);
} else if (animal instanceof Dog) {
    dogAction.accept((Dog) animal);
} else if (animal instanceof Fish) {
    fishAction.accept((Fish) animal);
} else if (animal instanceof Bird) {
    birdAction.accept((Bird) animal);
} else {
    throw new AssertionError(animal.getClass());
}

使用这个 Kotlin:

when (animal) {
    is Cat -> catAction.accept(animal)
    is Dog -> dogAction.accept(animal)
    is Fish -> fishAction.accept(animal)
    is Bird -> birdAction.accept(animal)
    else -> throw AssertionError(animal.javaClass)
}

在 Kotlin 中,您不需要强制转换,因为当编译器看到 is 检查实例类型时会自动生成 smart cast

同样在 Kotlin 中,您可以使用 Sealed Classes 来表示层次结构中的可能选项,然后编译器可以确定您是否已用尽所有情况,这意味着您不需要 when 语句中的 else

否则,该页面上适用的内容以及同一问题的其他常见答案对 Kotlin 来说是很好的信息。我认为在 Java 8、Scala 或 Kotlin 中看到实际的字面访问者模式并不常见,而是使用 lambda 和/或模式匹配的一些变体。

其他相关文章:

【讨论】:

  • 使用带有类型检查的大 if-else/when 子句是访问者模式试图避免的,不是吗?因此,只有第二篇相关文章 (phd-in) 中的解决方案似乎是有效的,因为那里使用了单一调度。但是使用带有 n 个 lambda 而不是一个访问者对象的接受方法听起来也不合理。如果您无权访问要双重分派的类,问题就会变得更加明显。然后你使用静态分派的扩展方法,或者......
  • 访问者模式提供了一种将数据与作用于该数据的逻辑分离的方法。双重调度是一个实现细节。
【解决方案2】:

这是一个不进行双重分派但确实实现了数据和作用于它的代码之间的分离的实现。

访问者的调度是使用 when 表达式“手动”完成的(这是详尽的),这需要较少的样板,因为不需要覆盖所有访问者中的 accept() 方法和多个 visit() 方法一个访客。

package visitor.addition
import visitor.addition.Expression.*

interface Visitor {
    fun visit(expression: Expression)
}

sealed class Expression {
    fun accept(visitor: Visitor) = visitor.visit(this)

    class Num(val value: Int) : Expression()
    class Sum(val left: Expression, val right: Expression) : Expression()
    class Mul(val left: Expression, val right: Expression) : Expression()
}

class PrintVisitor() : Visitor {
    val sb = StringBuilder()

    override fun visit(e: Expression) {
        val x = when (e) {
            is Num -> sb.append(e.value)
            is Sum -> stringify("+", e.left, e.right)
            is Mul -> stringify("*", e.left, e.right)
        }
    }

    fun stringify(name : String, left: Expression, right: Expression) {
        sb.append('(')
        left.accept(this); sb.append(name); right.accept(this)
        sb.append(')')
    }

}

fun main(args: Array<String>) {
    val exp = Sum(Mul(Num(9), Num(10)), Sum(Num(1), Num(2)))
    val visitor = PrintVisitor()
    exp.accept(visitor)

    println(visitor.sb) // prints: ((9*10)+(1+2))
}

【讨论】:

    【解决方案3】:

    为了解决这个问题,我会使用这样的东西:

    interface Visitable {
    
        fun accept(visitor: Visitor)
    }
    
    

    然后实现:

    class House : Visitable {
    
        override fun accept(visitor: Visitor) {
             visitor.visit(this)
        }
    }
    
    class Car : Visitable {
    
        override fun accept(visitor: Visitor) {
             visitor.visit(this)
        }
    }
    
    

    访客本身:

    interface Visitor {
    
        fun visit(entity: Car)
    
        fun visit(entity: House)
    }
    

    和实施:

    class Printer : Visitor {
    
        override fun visit(entity: Car) {
             println("Im in A Car")
        }
    
        override fun visit(entity: House) {
            println( "I'm in a House")
        }
    }
    

    用法:

    fun main(args: Array<String>) {
    
        val list = listOf<Visitable>(House(), Car())
    
        val printer = Printer()
    
        list.map { it.accept(printer) }
    }
    
    

    输出:

    I'm in a House
    Im in A Car
    

    【讨论】:

      【解决方案4】:

      可以使用伴随对象和 lambda 的组合来实现动态访问,如下所示:

      interface Visitable { fun visit()}
      
      class FooOne(): Visitable {
          val title1 = "111"
          companion object { var visit: (FooOne)->Unit  = {} }
          override fun visit() { FooOne.visit(this) }
      }
      
      class FooTwo(): Visitable {
          val title2 = "222"
          companion object { var visit: (FooTwo)->Unit  = {} }
          override fun visit() { FooTwo.visit(this) }
      }
      
      /* assign visitor functionality based on types */
      fun visitorStars() {
          FooOne.visit = {println("In FooOne: ***${it.title1}***") }
          FooTwo.visit = {println("In FooTwo: ***${it.title2}***") }
      }
      
      /* assign different visitor functionality */
      fun visitorHashes() {
          FooOne.visit = { println("In FooOne: ###${it.title1}###") }
          FooTwo.visit = {println("In FooTwo: ###${it.title2}###") }
      }
      
      fun main(args: Array<String>) {
          val foos = listOf<Visitable>(FooOne(), FooTwo())
          visitorStars()
          foos.forEach {it.visit()}
          visitorHashes()
          foos.forEach {it.visit()}
      }
      
      >>>
      In FooOne: ***111***
      In FooTwo: ***222***
      In FooOne: ###111###
      In FooTwo: ###222###
      

      【讨论】:

      • 为什么需要在伴生对象中添加一个 lambda?与类本身中的常规方法一样,它也可以正常工作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多