Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I reduce boilerplate with the visitor pattern in Swift?

I am implementing the visitor pattern in Swift 2.2 for a project at work.

So that I don't have to boil down my source code and to save me some time I will use an 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" }
}

The problem I have been trying to solve is to reduce the boilerplate on each class that derives from Planet. As you can see they all have same function duplicated func accept(visitor: PlanetVisitor) { visitor.visit(self) }.

I have tried putting a default implementation on the Planet protocol and implementing it on a base class and Swift does not seem to allow it due to compile time overload resolution.

Examples:

Default Implementation on Protocol:

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

Base Class:

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

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

Does anyone know of a way that the accept function could be made generic and applied automatically to each concrete class that derives from Planet? It is not a critical issue but it is a great puzzle!

like image 811
Sean Dawson Avatar asked Nov 23 '16 08:11

Sean Dawson


People also ask

Which design pattern is usually used with visitor design pattern?

Visitor design pattern is one of the behavioral design patterns. It is used when we have to perform an operation on a group of similar kind of Objects. With the help of visitor pattern, we can move the operational logic from the objects to another class.

When should you use the visitor pattern?

2 Answers. Save this answer. Show activity on this post. The visitor pattern is useful when you want to process a data structure containing different kinds of objects, and you want to perform a specific operation on each of them, depending on its type.

How does the visitor design pattern work?

The Visitor pattern represents an operation to be performed on the elements of an object structure without changing the classes on which it operates. This pattern can be observed in the operation of a taxi company. When a person calls a taxi company (accepting a visitor), the company dispatches a cab to the customer.


1 Answers

Short answer: not possible, and this is by design.

The visitor pattern intended for the case when you have stable number of planets, but unknown yet number of visitors. So you plan for the future extensions in visitors, writing this boilerplate once. Adding more visitors then is possible with no changes to planets.

In a large project you might go with code generation.


Not recommended, your alternative is a direct switch over planets, no boilerplate code needed:

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

This is error-prone, since you can easily forget planets. Visitor pattern forces you to write code for all cases, otherwise it won't compile.

like image 168
paiv Avatar answered Oct 11 '22 17:10

paiv