Как я могу уменьшить шаблон с помощью шаблона посетителя в Swift?

Я реализую шаблон посетителя в Swift 2.2 для рабочего проекта.

Чтобы мне не нужно было сокращать исходный код и сэкономить время, я буду использовать пример шаблона посетителей в 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? Это не критическая проблема, но это отличная головоломка!


person Sean Dawson    schedule 23.11.2016    source источник


Ответы (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"
    }
}

Это подвержено ошибкам, так как вы легко можете забыть о планетах. Шаблон посетителя заставляет вас писать код для всех случаев, иначе он не скомпилируется.

person paiv    schedule 15.02.2017
comment
Ты прав! По какой-то причине я думал, что другие языки, такие как C++, могут реализовать метод accept в базовом классе, но оказалось, что я ошибался. См.: stackoverflow.com/questions/17190873/ - person Sean Dawson; 15.02.2017

Читая ответ @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.

person user28434'mstep    schedule 21.02.2017
comment
Шаблон Visitor был изобретен частично для того, чтобы позволить кодирование в стиле enum в объектно-ориентированных языках. Учитывая, что в Swift отличная поддержка enum, шаблон Visitor является излишним. Если вы внимательно посмотрите на этот код, то увидите, что любые классы PlanetVisitor можно заменить функцией, которая напрямую принимает перечисление Planet. Я не вижу здесь никакой пользы от шаблона Visitor. - person hashemi; 21.03.2017