DEV Community

Cristian Francisco Meoño Canel
Cristian Francisco Meoño Canel

Posted on

Go Design Patterns : Observer

Factory

It is a behavior pattern that allows us to have a group of objects (observers) subscribed to another object (observable).

It is usually used to notify observers of the occurrence of an event.

The Goroutines provide us an quite efficient way to implement this design pattern.

Let's see this example.

  • First, we need to implement our observable object, this will have an array of observers.

In this example our observable object will be a "Topic" this will notify his observers of the availability of a product.

type Topic struct {
    Observers []Observer
    Name      string
    Available bool
}

//Constructor
func NewTopic(name string) *Topic {
    return &Topic{
        Name: name,
    }
}

//Update 'Available' value and notify it to Observers
func (i *Topic) UpdateAvailable(value bool) {
    i.Available = value
    i.Broadcast()
}

//Using Goroutines, will notify each observer of the array
func (i *Topic) Broadcast() {
    var wg sync.WaitGroup
    for _, obj := range i.Observers {
        wg.Add(1)
        go func(observer Observer) {
            defer wg.Done()
            observer.Notify(i.Name, i.Available)
        }(obj)
    }
    wg.Wait()
}

//Add a new observer to the array
func (i *Topic) Register(observer Observer) {
    i.Observers = append(i.Observers, observer)
}

Enter fullscreen mode Exit fullscreen mode
  • Then we need to define how "looks" our observer objects, this objects can have differents behaviors, in OOP we would usually use an abstract class for the Observer, in Go we can use an Interface where each object needs to implement in order to be an Observer.

In this example exists to types of Observers, EmailClient and SMSClient

type Observer interface {
    GetId() string //Id Getter
    Notify(name string, value bool) //Send the notification
}

type EmailClient struct {
    id string
}

func (eC *EmailClient) GetId() string {
    return eC.id
}

func (eC *EmailClient) Notify(name string, value bool) {

    if value {
        fmt.Printf("Sending email - %s available to client %s\n", name, eC.id)
    } else {
        fmt.Printf("Sending email - %s not available to client %s\n", name, eC.id)
    }

}

type SMSClient struct {
    id string
}

func (sC *SMSClient) GetId() string {
    return sC.id
}

func (sC *SMSClient) Notify(name string, value bool) {

    if value {
        fmt.Printf("Sending SMS - %s available to client %s\n", name, sC.id)
    } else {
        fmt.Printf("Sending SMS - %s not available to client %s\n", name, sC.id)
    }

}
Enter fullscreen mode Exit fullscreen mode

Running Our Code

func main() {
    nvidiaTopic := NewTopic("RTX 3080")
    firstObserver := &EmailClient{
        id: "100",
    }
    secondObserver := &SMSClient{
        id: "200",
    }
    nvidiaTopic.Register(firstObserver)
    nvidiaTopic.Register(secondObserver)
    nvidiaTopic.UpdateAvailable(true)
    nvidiaTopic.UpdateAvailable(false)
}
Enter fullscreen mode Exit fullscreen mode

Output

Sending SMS - RTX 3080 available to client 200
Sending email - RTX 3080 available to client 100
Sending SMS - RTX 3080 not available to client 200
Sending email - RTX 3080 not available to client 100

Program exited.
Enter fullscreen mode Exit fullscreen mode

Full Example

package main

import (
    "fmt"
    "sync"
)

type Topic struct {
    Observers []Observer
    Name      string
    Available bool
}

//Constructor
func NewTopic(name string) *Topic {
    return &Topic{
        Name: name,
    }
}

//Update 'Available' value and notify it to Observers
func (i *Topic) UpdateAvailable(value bool) {
    i.Available = value
    i.Broadcast()
}

//Using Goroutines, will notify each observer of the array
func (i *Topic) Broadcast() {
    var wg sync.WaitGroup
    for _, obj := range i.Observers {
        wg.Add(1)
        go func(observer Observer) {
            defer wg.Done()
            observer.Notify(i.Name, i.Available)
        }(obj)
    }
    wg.Wait()
}

//Add a new observer to the array
func (i *Topic) Register(observer Observer) {
    i.Observers = append(i.Observers, observer)
}

type Observer interface {
    GetId() string                  //Id Getter
    Notify(name string, value bool) //Send the notification
}

type EmailClient struct {
    id string
}

func (eC *EmailClient) GetId() string {
    return eC.id
}

func (eC *EmailClient) Notify(name string, value bool) {

    if value {
        fmt.Printf("Sending email - %s available to client %s\n", name, eC.id)
    } else {
        fmt.Printf("Sending email - %s not available to client %s\n", name, eC.id)
    }

}

type SMSClient struct {
    id string
}

func (sC *SMSClient) GetId() string {
    return sC.id
}

func (sC *SMSClient) Notify(name string, value bool) {

    if value {
        fmt.Printf("Sending SMS - %s available to client %s\n", name, sC.id)
    } else {
        fmt.Printf("Sending SMS - %s not available to client %s\n", name, sC.id)
    }

}

func main() {
    nvidiaTopic := NewTopic("RTX 3080")
    firstObserver := &EmailClient{
        id: "100",
    }
    secondObserver := &SMSClient{
        id: "200",
    }
    nvidiaTopic.Register(firstObserver)
    nvidiaTopic.Register(secondObserver)
    nvidiaTopic.UpdateAvailable(true)
    nvidiaTopic.UpdateAvailable(false)
}
Enter fullscreen mode Exit fullscreen mode

And that's all, see you for another post.

Top comments (0)