Observer

Observer design pattern is a one-to-many push-notification design, where the object notify all its observing objects when it experiences a change or an event. This is commonly seen in:

  • Application push notifications design (e.g. mobile application)
  • Graphical user interface (GUI) architecture
    • Example: the View/Template part in MVC / MTV frameworks

The diagram is as follows:

When to Deploy (Problem)

  • When an monolith application is too large that it requires abstractions between different components at framework level.
  • When a state change to one object requires notifications to other subscribed objects instead of depending on them.

Example Codes (Solution)

Here is an example in Go. Unlike other languages, Go requires one to create the Communicator structure that acts as the communication abstracts between 2 objects. It itself is a composite patterned structure where it has many subscribers, which is itself. The Communicator structure offers the Subscribe, Unsubscribe, Notify, and Update functions for the event notification and responding purposes.

In MVC, MVVM, or VIPER framework, all 4 event functions above are best known as:

  • SubscribeAddEventListener
  • UnsubscribeRemoveEventListener / DeleteEventListener
  • NotifyActionPerformed
  • UpdateActionEvent
package main

import (
        "fmt"
)

/*********************************************************************
 * INTERFACE - COMMUNICATOR                                  *
 ********************************************************************/
// Communicator structure for managing subscriptions and notifications
type Communicator struct {
        Label       string
        subscribers []*Communicator
        action      func(interface{})
}

// New Communicator creates a Communicator object
func NewCommunicator(label string) *Communicator {
        return &Communicator{
                Label:       label,
                subscribers: []*Communicator{},
                action:      nil,
        }
}

// Subscribe is for the communicator to add another communicator
func (c *Communicator) Subscribe(ch *Communicator) {
        if ch != nil {
                fmt.Printf("%v event: %v subbed.\n", c.Label, ch.Label)
                c.subscribers = append(c.subscribers, ch)
        }
}

// Unsubscribe is for the communicator to remove another communicator
func (c *Communicator) Unsubscribe(ch *Communicator) {
        if ch == nil {
                return
        }

        for i, s := range c.subscribers {
                if s == ch {
                        fmt.Printf("%v event: %v unsubbed.\n", c.Label, s.Label)
                        c.subscribers = append(c.subscribers[:i],
                                c.subscribers[i+1:]...)
                        break
                }
        }
}

// Notify is for the communicator to notify all its subscribers with an
// event.
func (c *Communicator) Notify(event interface{}) {
        for _, s := range c.subscribers {
                s.Update(event)
        }
}

// Update runs the executions when the communicator is notified
func (c *Communicator) Update(event interface{}) {
        if c.action != nil {
                c.action(event)
        }
}

// Register adds an action to the communicator for Update to execute
func (c *Communicator) Register(fx func(interface{})) {
        c.action = fx
}

/*********************************************************************
 * SUBJECT CONCRETE                                                  *
 ********************************************************************/
// Subject is the main even trigger. It creates the Channel communicator.
type Subject struct {
        Channel *Communicator
}

func NewSubject(label string) *Subject {
        return &Subject{
                Channel: NewCommunicator(label),
        }
}

func (s *Subject) PressedButton() {
        fmt.Printf("Subject: user pressed button\n")
        s.Channel.Notify("Button pressed")
}

/*********************************************************************
 * OBSERVER CONCRETE                                                 *
 ********************************************************************/
// Observer is the main subscriber to the subject. It has its own
// Channel.
type Object struct {
        Channel *Communicator
}

func NewObject(label string) *Object {
        o := Object{
                Channel: NewCommunicator(label),
        }
        o.Channel.action = o.Update
        return &o
}

func (o *Object) Update(event interface{}) {
        fmt.Printf("%v Update: %v\n", o.Channel.Label, event)
}

/*********************************************************************
 * MAIN ACTION                                                       *
 ********************************************************************/
func main() {
        s := NewSubject("subject 1")
        o1 := NewObject("object 1")
        o2 := NewObject("object 2")

        // o1 and o2 observes s
        s.Channel.Subscribe(o1.Channel)
        s.Channel.Subscribe(o2.Channel)

        // s received an event
        s.PressedButton()

        // o2 stop observing
        s.Channel.Unsubscribe(o2.Channel)

        // s received an event
        s.PressedButton()
}

// Output:
// subject 1 event: object 1 subbed.
// subject 1 event: object 2 subbed.
// Subject: user pressed button
// object 1 Update: Button pressed
// object 2 Update: Button pressed
// subject 1 event: object 2 unsubbed.
// Subject: user pressed button
// object 1 Update: Button pressed

Expected Outcome (Consequences)

  • Loosely coupled between the subject and observer, enhancing reusability.
  • Support broadcast communications.
  • Notification leads to further updates, cascading effects.

That's all about observer design pattern.