Skip to content

Instantly share code, notes, and snippets.

@francoishill
Last active April 12, 2020 20:45
Show Gist options
  • Save francoishill/f0624e7760aacdc96b42 to your computer and use it in GitHub Desktop.
Save francoishill/f0624e7760aacdc96b42 to your computer and use it in GitHub Desktop.
Visitor Pattern in Golang
package main
//Thanks to http://ecs.victoria.ac.nz/foswiki/pub/Main/TechnicalReportSeries/ECSTR11-01.pdf
import (
"fmt"
)
//We will have multiple car parts
type CarPart interface {
Accept(CarPartVisitor)
}
type Wheel struct {
Name string
}
func (this *Wheel) Accept(visitor CarPartVisitor) {
visitor.visitWheel(this)
}
type Engine struct {}
func (this *Engine) Accept(visitor CarPartVisitor) {
visitor.visitEngine(this)
}
type Car struct {
parts []CarPart
}
func NewCar() *Car {
this := new(Car)
this.parts = []CarPart{
&Wheel{"front left"},
&Wheel{"front right"},
&Wheel{"rear right"},
&Wheel{"rear left"},
&Engine{}}
return this
}
func (this *Car) Accept(visitor CarPartVisitor) {
for _, part := range this.parts {
part.Accept(visitor)
}
}
//Interface of the visitor
type CarPartVisitor interface {
visitWheel(wheel *Wheel)
visitEngine(engine *Engine)
}
//Concrete Implementation of the visitor
type GetMessageVisitor struct{
Messages []string
}
func (this *GetMessageVisitor) visitWheel(wheel *Wheel) {
this.Messages = append(this.Messages, fmt.Sprintf("Visiting the %v wheel\n", wheel.Name))
}
func (this *GetMessageVisitor) visitEngine(engine *Engine) {
this.Messages = append(this.Messages, fmt.Sprintf("Visiting engine\n"))
}
//Usage of the visitor
func main() {
car := NewCar()
visitor := new(GetMessageVisitor)
car.Accept(visitor)
fmt.Println(visitor.Messages)
}
@Julio-Guerra
Copy link

Julio-Guerra commented Apr 25, 2017

Unfortunately, this example is too simple, and serious limitations come when dealing with real cases, all due to language's limitations:

  • this solution only works if both the visitor interface and the structures are in the same package to avoid import cycles (they both need each other).
  • since Go hasn't virtual method calls, you can't write a default visitor (the one which walks through the structures) and make other visitors inherit from it to override some methods only. My solution here is having the visitor passed by argument through an interface type so that visitor calls get dispatched to the final visitor. So no embedding, which looks like inheritance, because once you call a method of the embedded object, you can no longer call methods of the "embedder".

The end result is like passing a this pointer:

type CarVisitor interface {
    VisitCar(v CarVisitor, car *Car)
    VisitWheel(v CarVisitor, wheel *Wheel)
    VisitEngine(v CarVisitor, engine *Engine)
}

type DefaultVisitor struct {}
func (_ *DefaultVisitor) VisitCar(v CarVisitor, car *Car) {
   for _, part := range car.Parts {
        part.Accept(v)
    }
}
func (_ *DefaultVisitor) VisitWheel(v CarVisitor, wheel *Wheel) {
}
func (_ *DefaultVisitor) VisitEngine(v CarVisitor, engine *Engine) {
}

type GetMessageVisitor struct{
    DefaultVisitor
    Messages []string
}
func (this *GetMessageVisitor) VisitWheel(_ CarVisitor, wheel *Wheel) {
    this.Messages = append(this.Messages, fmt.Sprintf("Visiting the %v wheel\n", wheel.Name))
}
func (this *GetMessageVisitor) VisitEngine(_ CarVisitor, engine *Engine) {
    this.Messages = append(this.Messages, fmt.Sprintf("Visiting engine\n"))
}

func main() {
    car := NewCar()
    visitor := &GetMessageVisitor{}
    visitor.VisitCar(visitor, car)
    fmt.Println(visitor.Messages)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment