Visitor

Visitor design pattern is usually used to abstract functionality that is usable across hierarchy of "element" objects. It is a different way to encapsulate codes next to inheritance technique. The idea is that a class should not be "polluted" by heterogeneous data structures. Example from StackOverflow:

abstract class Fruit { }
class Orange : Fruit { }
class Apple : Fruit { }
class Banana : Fruit { }

var fruits = new Fruit[]
    { new Orange(), new Apple(), new Banana(),
      new Banana(), new Banana(), new Orange() };

List<Orange> oranges = new List<Orange>();
List<Apple> apples = new List<Apple>();
List<Banana> bananas = new List<Banana>();

foreach (Fruit fruit in fruits) {
    if (fruit is Orange)
        oranges.Add((Orange)fruit);
    else if (fruit is Apple)
        apples.Add((Apple)fruit);
    else if (fruit is Banana)
        bananas.Add((Banana)fruit);
}

So if we want to add another fruit type, it will force the developer to look all over the places and add the new addition. Therefore, visitor encapsulation design pattern is best suited for this case. Here is the design diagram:

When to Use (Problem)

  • The problem requires you to maintain data type but forcing you to query each data type for an operation.
  • The problem requires you to encapsulate 2 data classes away from each other while maintaining interaction.

Example (Solution)

Visitor pattern makes no sense in Go implementation due to Go facilitating interface feature. However, for the academic learning purpose, here is an example:

package main

import (
        "fmt"
)

/****************************************************************
 * Visitor                                                      *
 ****************************************************************/
type updateLocation interface {
        UpdateLocation(gps string)
}

type Cab struct {
}

func (c *Cab) engageGear() {
        fmt.Printf("cab engaging gear.\n")
}

func (c *Cab) driveToDestination() {
        fmt.Printf("driving to destination.\n")
}

func (c *Cab) disengageGear() {
        fmt.Printf("arrived destination. Stopping\n")
}

func (c *Cab) Transport(customer updateLocation) {
        c.engageGear()
        c.driveToDestination()
        c.disengageGear()
        customer.UpdateLocation("destination")
}

/****************************************************************
 * Element                                                      *
 ****************************************************************/
type Transport interface {
        Transport(customer updateLocation)
}

type Dispatcher interface {
        Dispatch() Transport
}

type Customer struct {
        gps string
        car Transport
}

func (c *Customer) UpdateLocation(gps string) {
        c.gps = gps
}

func (c *Customer) SendCab(dispatcher Dispatcher) {
        c.car = dispatcher.Dispatch()
}

func (c *Customer) SignalTravel() {
        if c.car == nil {
                // error. Not in cab.
                return
        }
        c.car.Transport(c)
}

/****************************************************************
 * Partitioner                                                  *
 ****************************************************************/
type Company struct {
        Carriers []*Cab
}

func (c *Company) Dispatch() Transport {
        return c.Carriers[1]
}

/****************************************************************
 * Main Code                                                    *
 ****************************************************************/
func main() {
        d := &Company{
                Carriers: []*Cab{
                        &Cab{},
                        &Cab{},
                        &Cab{},
                },
        }

        c := &Customer{
                gps: "origin",
        }
        c.SendCab(d)
        c.SignalTravel()
}

// Output:
// cab engaging gear.
// driving to destination.
// arrived destination. Stopping

Notice that:

  1. the visitor (Cab) and accepter (Customer) are completely isolated from one another.
  2. the visitor (Cab) is a sub-class to the Partitioner (Dispatcher).

The visitor (Cab) does what it needs to be done transporting the customer without meddling customer structure and its data definition. It can accept various kind of customers as long as customer has the UpdateLocation() function interface offered to them.

Expected Outcome (Consequences)

  1. The visitor design pattern successfully separated the visitor, origin data, and partitioner.
  2. Visitor design pattern is an overkill for compiler facilitating functional interface, inheritance and encapsulation. It is meant for compiler without those features.
  3. Alternatively, one can stick to other design pattern for simple design.