DEV Community

loading...
Cover image for Mediator Design Pattern in Go

Mediator Design Pattern in Go

tomassirio profile image Tomas Sirio ・4 min read

Hey there!

We are getting to the end of this series with some behavioral patterns in Go. Today I'm bringing you the Mediator Pattern.

When to use this pattern?

  • When you detect that you have a couple of classes that are tightly coupled.

  • When you notice that you are creating a lot of subclasses in order to reuse a basic behavior in different contexts.

  • When you can't reuse a component in a different program because it’s too dependent on other components.

How to implement it?

  1. Identify a class (or classes) that are tightly coupled which would benefit from being more independent and/or free.

  2. Define a mediator interface with a method that your components will use to communicate with each other through the mediator.

  3. Implement the Mediator class. This class would benefit from getting a reference (pointer) to each of the components which will use this class.

  4. The components should implement a reference to the Mediator.

  5. Modify the components method so that they communicate to the mediator instead of methods on other components.

Our Example

Let's create an Airport Runway with its control tower as our mediator

Alt Text

Following our steps from the previous subtitle, we need to detect our coupled classes:

type AirTransport struct {
    PilotsName string
    Type       string
    Runway     *Runway
    runwayLog  []string
}

func NewHelicopter(pilotsName string) *AirTransport {
    return &AirTransport{PilotsName: pilotsName, Type: "Helicopter"}
}

func NewPlane(pilotsName string) *AirTransport {
    return &AirTransport{PilotsName: pilotsName, Type: "Plane"}
}

func NewUFO(pilotsName string) *AirTransport {
    return &AirTransport{PilotsName: pilotsName, Type: "UFO"}
}
Enter fullscreen mode Exit fullscreen mode

We have a lot of subclasses that will actually behave similarly and that will need to communicate among themselves in order to coordinate how will they land on the runway.

The issue here is that if we create a lot of methods for communication among the classes, we will need to keep adding validations as soon as we add new AirTransports and the objects will become too convoluted and dependant.

So let's create an interface for our communication method:

type ATCMediator interface {
    communicate(airTransport *AirTransport, message string)
}
Enter fullscreen mode Exit fullscreen mode

Now we need our Runway which will implement the mediator interface:

type Runway struct {
    airTransports []*AirTransport
}

func (r *Runway) Communicate(src *AirTransport, message string) {
    for _, at := range r.airTransports {
        if at != src {
            at.Receive(at, message)
        }
    }
}

func (r *Runway) Message(src, dst *AirTransport, message string) {
    for _, at := range r.airTransports {
        if at == dst {
            at.Receive(src, message)
        }
    }
}

func (r *Runway) Join(at *AirTransport) {
    joinMsg := at.PilotsName + " joined the Runway"
    r.Communicate(at, joinMsg)

    at.Runway = r
    r.airTransports = append(r.airTransports, at)
}
Enter fullscreen mode Exit fullscreen mode

This Runway is implementing the communication method from the declared interface and also adds two new methods to Message The AirTransports privately and a method to join the Runway.

So now we can actually implement our communication methods for the AirTransports:

func (at *AirTransport) Receive(sender *AirTransport, message string) {
    s := fmt.Sprintf("%s (Transport: %s): '%s'", sender.PilotsName, sender.Type, message)
    fmt.Printf("[%s's runway log] %s\n", sender.PilotsName, s)
    at.runwayLog = append(at.runwayLog, s)
}

func (at *AirTransport) Say(message string) {
    at.Runway.Communicate(at, message)
}

func (at *AirTransport) PrivateMessage(dst *AirTransport, message string) {
    if at != dst {
        at.Runway.Message(at, dst, message)
    }
}
Enter fullscreen mode Exit fullscreen mode

So what we are doing here is relying on the Runway as a mediator for every message sent and received granting a log for the communication and a separate log for every AirTransport.

That's mostly the implementation. So let's jump into the instantiation:

    r := &Runway{}

    h := NewHelicopter("Jack")
    p := NewPlane("Tom")
    u := NewUFO("ET")

    r.Join(h)
    r.Join(p)
    r.Join(u)

    h.Say("Trying to land on Runway")
    p.Say("Acknowledged")
    u.Say("Phone home")

    h.PrivateMessage(p, "Is there an UFO in the Runway")
    p.PrivateMessage(h, "Dude, there's an UFO in the Runway")

    u.Say("I shouldn't be able to read your PM's but I can... ")
Enter fullscreen mode Exit fullscreen mode

We are creating a simple Runway and three different AirTransports. We are also making them communicate among them. This brings us to the results:

[Jack's runway log] Jack (Transport: Helicopter): 'Tom joined the Runway'
[Jack's runway log] Jack (Transport: Helicopter): 'ET joined the Runway'
[Tom's runway log] Tom (Transport: Plane): 'ET joined the Runway'
[Tom's runway log] Tom (Transport: Plane): 'Trying to land on Runway'
[ET's runway log] ET (Transport: Ovni): 'Trying to land on Runway'
[Jack's runway log] Jack (Transport: Helicopter): 'Acknowledged'
[ET's runway log] ET (Transport: Ovni): 'Acknowledged'
[Jack's runway log] Jack (Transport: Helicopter): 'Phone home'
[Tom's runway log] Tom (Transport: Plane): 'Phone home'
[Jack's runway log] Jack (Transport: Helicopter): 'Is there an Ovni in the Runway'
[Tom's runway log] Tom (Transport: Plane): 'Dude, there's an Ovni in the Runway'
[Jack's runway log] Jack (Transport: Helicopter): 'I shouldn't be able to read your PM's but I can... '
[Tom's runway log] Tom (Transport: Plane): 'I shouldn't be able to read your PM's but I can... '
Enter fullscreen mode Exit fullscreen mode

As you can see, in the terminal we will get all the logs from the pilots (or AirTransports) who are communicating over the mediator. The messages are repeated because they are broadcasted through all the users.

That's mostly it for this example
Happy Coding!

Discussion (0)

pic
Editor guide