Паттерны проектирования Go : Наблюдатель

Это модель поведения, которая позволяет нам иметь группу объектов (наблюдателей), подписанных на другой объект (наблюдаемый).

Обычно он используется для уведомления наблюдателей о наступлении события.

Гороутины предоставляют нам довольно эффективный способ реализации этого паттерна проектирования.

Давайте рассмотрим этот пример.

  • Во-первых, нам нужно реализовать объект observable, который будет содержать массив наблюдателей.

В данном примере нашим наблюдаемым объектом будет «Topic», который будет уведомлять своих наблюдателей о наличии товара.

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)
}

Вход в полноэкранный режим Выход из полноэкранного режима
  • Затем нам нужно определить, как «выглядят» наши объекты-наблюдатели, эти объекты могут иметь различное поведение, в ООП мы обычно используем абстрактный класс для наблюдателя, в Go мы можем использовать интерфейс, который каждый объект должен реализовать, чтобы быть наблюдателем.

В данном примере существует несколько типов наблюдателей, EmailClient и 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 %sn", name, eC.id)
    } else {
        fmt.Printf("Sending email - %s not available to client %sn", 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 %sn", name, sC.id)
    } else {
        fmt.Printf("Sending SMS - %s not available to client %sn", 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)
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Вывод

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.
Ввести полноэкранный режим Выход из полноэкранного режима

Пример полного экрана

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 %sn", name, eC.id)
    } else {
        fmt.Printf("Sending email - %s not available to client %sn", 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 %sn", name, sC.id)
    } else {
        fmt.Printf("Sending SMS - %s not available to client %sn", 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)
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вот и все, до встречи в следующем посте.

Оцените статью
Procodings.ru
Добавить комментарий