Mediator

Mediator pattern is a centralized communication design to provide maximum control over data/control path flow. There is a "mediator" or "traffic controller" between the client and the corresponding actions. Here is a modified descriptive diagram for Mediator:

When to Use (Problem)

  • There is a need to perform traffic control over each actions (e.g. user permissions in Unix: owner, group, and world).
  • There is a need to perform filtering.

Example (Solution)

Here is an example for Go:

package main

import (
        "fmt"
        "strings"
)

// Mediator acts as the traffic controller for time reservation.
type Mediator struct {
        list map[string]int
}

// NewMediator creats the new mediator object.
func NewMediator() *Mediator {
        return &Mediator{
                list: map[string]int{},
        }
}

// Reserve is to make a time reservation into Mediator time list.
func (m *Mediator) Reserve(id int, time string) bool {
        _, ok := m.list[time]
        if !ok {
                fmt.Printf("mediator: executor %v, you got the ball for %v.\n",
                        id,
                        time)
                m.list[time] = id
                return true
        }
        fmt.Printf("mediator: negative executor %v. Bad timing %v.\n", id, time)
        return false
}

// Checkout is to use the reserved or any unoccupied time slot.
func (m *Mediator) CheckOut(id int, time string) bool {
        v, ok := m.list[time]
        if !ok {
                // unoccupied
                fmt.Printf("mediator: executor %v, you got the ball.\n", id)
                return true
        }

        if v == id {
                // owner using the reserved list
                fmt.Printf("mediator: executor %v, you got the ball.\n", id)
                delete(m.list, time)
                return true
        }
        fmt.Printf("mediator: negative, executor %v. You can't for now.\n", id)
        return false
}



// Executor is the operator that execute a task based on Mediator allowed time
// time.
type Executor struct {
        id      int
        central *Mediator
}

// Execute is the actual program implementations.
func (e *Executor) Execute() {
        fmt.Printf("executor %v: using asset.\n", e.id)
}

// Request is to request a reserved time slot from the Mediator
func (e *Executor) Request(time ...string) {
        for _, t := range time {
                fmt.Printf("executor %v: central, reserve %v, over.\n",
                        e.id,
                        t)
                if e.central.Reserve(e.id, t) {
                        fmt.Printf("executor %v: successful reserved %v.\n",
                                e.id,
                                t)
                        return
                }
        }
        fmt.Printf("executor %v: failed to reserve all times: %v.\n",
                e.id,
                strings.Join(time, ", "))
}

// Use is to request a time slot for usage.
func (e *Executor) Use(time string) {
        fmt.Printf("executor %v: central, request usage at %v, over.\n",
                e.id,
                time)
        if !e.central.CheckOut(e.id, time) {
                fmt.Printf("executor %v: failed to request usage at %v.\n",
                        e.id,
                        time)
                return
        }
        fmt.Printf("executor %v: successful request usage at %v.\n",
                e.id,
                time)
        e.Execute()
}



// Actual
func main() {
        m := NewMediator()

        e1 := &Executor{
                id:      1124,
                central: m,
        }

        e2 := &Executor{
                id:      7274,
                central: m,
        }

        e1.Request("0000", "0125", "0233")
        e2.Request("0000", "0200", "0400")

        e2.Use("0000")
        e1.Use("0000")
        e2.Use("0210")
        e2.Use("0200")
        e2.Use("0400")
}

// Output:
// executor 1124: central, reserve 0000, over.
// mediator: executor 1124, you got the ball for 0000.
// executor 1124: successful reserved 0000.
// executor 7274: central, reserve 0000, over.
// mediator: negative executor 7274. Bad timing 0000.
// executor 7274: central, reserve 0200, over.
// mediator: executor 7274, you got the ball for 0200.
// executor 7274: successful reserved 0200.
// executor 7274: central, request usage at 0000, over.
// mediator: negative, executor 7274. You can't for now.
// executor 7274: failed to request usage at 0000.
// executor 1124: central, request usage at 0000, over.
// mediator: executor 1124, you got the ball.
// executor 1124: successful request usage at 0000.
// executor 1124: using asset.
// executor 7274: central, request usage at 0210, over.
// mediator: executor 7274, you got the ball.
// executor 7274: successful request usage at 0210.
// executor 7274: using asset.
// executor 7274: central, request usage at 0200, over.
// mediator: executor 7274, you got the ball.
// executor 7274: successful request usage at 0200.
// executor 7274: using asset.
// executor 7274: central, request usage at 0400, over.
// mediator: executor 7274, you got the ball.
// executor 7274: successful request usage at 0400.
// executor 7274: using asset.

Notice the conversation between executor 7274 and 1124 are coordinated by a mediator? They are both seeking time to run their own Execute. When 1124 first reserved for 0000, 7274 failed to request for it. Then, when 7274 tries to use 0000 time slot, mediator denies it again and only allows the owner 1124 to use it.

However, mediator allows 7274 to use any other time slots since they are unoccupied and unreserved.

Expected Outcome (Consequences)

  • A central "mediator" coordinates the executions at a given moment, be it time or access permission.
  • A central "mediator" does not know what to execute; it only coordinates the executors.
  • All executors request permission from the "mediator" before execution.
  • "Mediator" can be complicated to scale if not done properly (e.g. mistaken as a controller class).