Go Visitor Pattern
The visitor pattern allows us to extend the behaviour of various objects through a common interface.
It is particularly powerful when working with the composite pattern or any kind of graph execution where we want to keep our graph traversal logic seperate from our handling logic.
The Pattern
To show how the visitor pattern works, we will extend arbitrary shapes using visitors.
For example, we may have a Shape class which:
- accepts a
Visitor - is implemented by
CircleandRectangle
---
title: Class diagram
---
classDiagram
Shape <|-- Circle
Shape <|-- Rectangle
class Shape{
<<Interface>>
+Accept(Visitor)
}
class Circle{
+float64 Radius
}
class Rectangle{
+float64 Length
+float64 Width
}
class Visitor{
<<Interface>>
+DoCircle(Circle)
+DoRectangle(Rectangle)
}
Say we define an Area visitor:
---
title: Area Visitor
---
classDiagram
Visitor <|-- Area
class Visitor{
<<Interface>>
+DoCircle(Circle)
+DoRectangle(Rectangle)
}
class Area{
<<Interface>>
+DoCircle(Circle)
+DoRectangle(Rectangle)
+Calculate(Shape) float64
}
If we call Area::Calculate we get:
sequenceDiagram
participant Caller
participant Area
participant Circle
Caller->>Area: Calculate(Circle)
Area->>Circle: Accept(self)
Circle->>Area: DoCircle(self)
Area-->>Caller: result
The call to Accept and DoCircle is known as double dispatch and allows the Caller to not know that the Shape it has sent Area is infact a Circle.
Go Code
In go this looks like:
type Visitor interface {
DoCircle(Circle)
DoRectangle(Rectangle)
}
type Shape interface {
Accept(Visitor)
}
type Circle struct {
float64 Radius
}
func (c *Circle) Accept(v Visitor) {
v.DoCircle(c)
}
type Rectangle struct {
float64 Length
float64 Width
}
func (r *Rectangle) Accept(v Visitor) {
v.DoRectangle(c)
}
We are now setup to extend Circle and Rectangle shapes as much as we like.
An Area visitor will look like:
type Area struct {
float64 result
}
func (v *Area) Calculate(s Shape) float64 {
s.Visit(v)
return v.result
}
func (av *Area) DoCircle(c *Circle) {
v.result = math.Pi * math.Pow(c.Radius,2)
}
func (v *Area) DoRectangle(r *Rectangle) {
v.result = r.Length * r.Height
}
Another example is a Circumference visitor:
type Circumference struct {
float64 result
}
func (v *Circumference) Calculate(s Shape) float64 {
s.Visit(v)
return v.result
}
func (a *Circumference) DoCircle(c *Circle) {
v.result = 2.0 * math.Pi * c.Radius
}
func (v *Circumference) DoRectangle(r *Rectangle) {
v.result = 2.0 * (r.Length + r.Height)
}