在 Kotlin 中实现访问者模式的最佳方式

Best way to implement visitor pattern in Kotlin

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

编辑澄清:我有一个 AST 里面有许多(~30)种类型的节点。目前每个 class 都实现了自己的 print() 方法,我想将其分解到一个单独的打印机 class 中。有了访问者模式,添加其他 AST 遍历 classes 会更清晰,其中会有几个。

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

The additions made to the Java language do not render every old concept outdated. In fact, the Visitor pattern is very good at supporting adding of new operations.

Kotlin 也是如此。像 Java 8 它有 Lambdas, SAM conversions, and interfaces that allow default implementations

一个变化是如果你正在做 class 实例类型检查,而不是对每个 instanceof 检查使用一个大的 if 语句,使用 Kotlin 中的 when expression :

在同一 Whosebug 页面的不同答案中,它讨论了正在使用的 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 来表示层次结构中的可能选项,然后编译器可以确定您是否已经用尽所有情况,这意味着您不需要 else when声明。

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

其他相关文章:

可以使用伴随对象和 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###

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

访问者的调度是 "by hand" 使用 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))
}

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

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