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.
Identify a class (or classes) that are tightly coupled and would benefit from being more independent and/or free.
Define a mediator interface with a method that your components will use to communicate with each other through the mediator.
Implement the Mediator class. This class will benefit from getting a reference (pointer) to each of the components that will use this class.
The components should implement a reference to the Mediator.
Modify the components method so that they communicate to the mediator instead of methods on other components.
Let’s create an Airport Runway with its control tower as our mediator:
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"}
}
We have a lot of subclasses that will behave similarly and 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 or the objects will become too convoluted and dependent.
So, let’s create an interface for our communication method
:
type ATCMediator interface {
communicate(airTransport *AirTransport, message string)
}
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)
}
This Runway
implements the communication method from the declared interface, adds two new methods to message the AirTransports
privately, and adds a method to join the Runway.
So now we can implement our communication methods for the AirTransports
:e:
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)
}
}
What we are doing here is relying on the Runway as a mediator for every message sent and received in order to grant a log for 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... ")
We are creating a simple Runway and three different AirTransports and making them communicate among themselves. This brings us to the results:
package mainimport "fmt"type AirTransport struct {PilotsName stringType stringRunway *RunwayrunwayLog []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"}}type ATCMediator interface {communicate(airTransport *AirTransport, message string)}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 = rr.airTransports = append(r.airTransports, at)}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)}}func main() {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... ")}
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– Happy Coding!
Check out the four other design patterns in GO:
Free Resources