如何在Swift中使用访问者模式来减少样板代码?

6
我正在工作中实现Swift 2.2中的访问者模式。为了不必简化源代码并节省时间,我将使用Oktawian Chojnacki在Swift中访问者模式的示例
protocol PlanetVisitor {
    func visit(planet: PlanetAlderaan)
    func visit(planet: PlanetCoruscant)
    func visit(planet: PlanetTatooine)
}

protocol Planet {
    func accept(visitor: PlanetVisitor)
}

class PlanetAlderaan: Planet {
    func accept(visitor: PlanetVisitor) { visitor.visit(self) }
}
class PlanetCoruscant: Planet {
    func accept(visitor: PlanetVisitor) { visitor.visit(self) }
}
class PlanetTatooine: Planet {
    func accept(visitor: PlanetVisitor) { visitor.visit(self) }
}

class NameVisitor: PlanetVisitor {
    var name = ""

    func visit(planet: PlanetAlderaan)  { name = "Alderaan" }
    func visit(planet: PlanetCoruscant) { name = "Coruscant" }
    func visit(planet: PlanetTatooine)  { name = "Tatooine" }
}

我一直在努力解决的问题是减少每个派生自Planet的类上的样板代码。 正如您所看到的,它们都有相同的重复函数func accept(visitor: PlanetVisitor) { visitor.visit(self) }
我尝试在Planet协议上放置默认实现并在基类上实现它,但Swift似乎不允许由于编译时重载解析。
示例:
协议上的默认实现:
extension Planet {
    func accept(visitor: PlanetVisitor) { visitor.visit(self) }
}

基类:
class PlanetBase: Planet {
    func accept(visitor: PlanetVisitor) { visitor.visit(self) }
}

class PlanetAlderaan: PlanetBase {}
class PlanetCoruscant: PlanetBase {}
class PlanetTatooine: PlanetBase {}

有没有办法使accept函数变成通用的,并自动应用于从Planet派生出的每个具体类?这不是一个关键问题,但它是一个很大的难题!

2个回答

2

简短回答:这是不可能的,而且是故意设计的。

访问者模式旨在处理稳定数量的星球,但访问者数量尚未确定的情况。因此,您可以编写此样板代码以计划将来的扩展访问者。添加更多访问者时,星球无需进行任何更改。

在大型项目中,您可以使用代码生成。


不建议,您的替代方案是直接切换星球,无需使用样板代码:

func foo(planet: Planet) {
    if planet is PlanetAlderaan {
        name = "Alderaan"
    }
    else if planet is PlanetCoruscant {
        name = "Coruscant"
    }
    else if planet is PlanetTatooine {
        name = "Tatooine"
    }
}

这种方法容易出错,因为你很容易忘记某些情况。访问者模式强制你编写所有情况的代码,否则它将无法编译。


你是对的!由于某种原因,我认为其他语言(如C++)可以在基类中实现accept方法,但事实证明我错了。请参见:https://dev59.com/G3PYa4cB1Zd3GeqPjX7M - Sean Dawson

-1

看了 @paiv 的回答,我想到了一个可以在避免忘记大小写问题的同时减少样板代码的方法:

enum Planet {
    case alderaan
    case coruscant
    case tatooine

    func accept(visitor: PlanetVisitor) {
        visitor.visit(planet: self)
    }
}

protocol PlanetVisitor {
    func visit(planet: Planet)
}

class NameVisitor: PlanetVisitor {
    var name = ""

    func visit(planet: Planet) {
        switch planet {
        case .alderaan:
            name = "Alderaan"
        case .coruscant:
            name = "Coruscant"
        case .tatooine:
            name = "Tatooine"
        }
    }
}

如果您在switch语句中不使用default,那么编译器会保证只要有任何一个case没有被处理,代码就无法编译通过。

但我认为其他一些样板代码可能会迁移到Planet类型内部。


1
访问者模式的发明部分是为了允许在面向对象语言中进行枚举样式编码。鉴于Swift具有出色的枚举支持,因此访问者模式是多余的。如果您仔细查看此代码,任何“PlanetVisitor”类都可以替换为直接使用“Planet”枚举的函数。我在这里看不到访问者模式的任何好处。 - hashemi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接