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

时间:2016-11-23 08:21:30

标签: swift design-patterns swift2 visitor-pattern

我正在Swift 2.2中为工作中的项目实现访问者模式。

因此,我不必简化我的源代码,并节省一些时间,我会使用example of visitor pattern in swift by Oktawian Chojnacki

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 个答案:

答案 0 :(得分: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"
    }
}

这很容易出错,因为你很容易忘记行星。访问者模式强制您为所有情况编写代码,否则它不会编译。

答案 1 :(得分:-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"
        }
    }
}

如果您未在default中使用switch,则可以保证编译器不会在没有处理任何案例的情况下编译代码。

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