Menu
Coddy logo textTech

Padrão Observer

Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 89 de 107.

O padrão Observer define uma relação de um para muitos entre objetos. Quando um objeto (o sujeito) muda de estado, todos os seus dependentes (observadores) são notificados automaticamente. Isso é ideal para sistemas de eventos, notificações ou qualquer cenário onde múltiplos componentes precisam reagir a mudanças.

Em Go, implementamos este padrão usando uma interface para observadores e uma struct subject que gerencia as inscrições:

type Observer interface {
    Update(event string)
}

type Subject struct {
    observers []Observer
}

func (s *Subject) Subscribe(o Observer) {
    s.observers = append(s.observers, o)
}

func (s *Subject) Notify(event string) {
    for _, observer := range s.observers {
        observer.Update(event)
    }
}

Qualquer tipo que implemente a interface Observer pode se inscrever para receber notificações:

type EmailAlert struct{ Address string }

func (e EmailAlert) Update(event string) {
    fmt.Printf("Email to %s: %s\n", e.Address, event)
}

type Logger struct{}

func (l Logger) Update(event string) {
    fmt.Println("Log:", event)
}

O subject notifica todos os observers sem conhecer seus tipos concretos:

subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})

subject.Notify("Order placed")
// E-mail para user@example.com: Order placed
// Log: Order placed

Este padrão desacopla o sujeito de seus observadores, facilitando a adição de novos tipos de observadores sem modificar o código existente. É comumente utilizado em arquiteturas orientadas a eventos, frameworks de GUI e sistemas de mensageria pub/sub.

challenge icon

Desafio

Fácil

Vamos construir um sistema de alerta de preço de ações usando o padrão Observer! Você criará um sistema onde múltiplos serviços de alerta recebem notificações automaticamente sempre que o preço de uma ação muda—perfeito para demonstrar como os observadores podem reagir a eventos sem que o sujeito conheça seus tipos concretos.

Você organizará seu código em três arquivos:

  • observer.go: Defina sua interface de observador e os tipos de observadores concretos.

    Crie uma interface Observer com um método Update(stockName string, price float64) que recebe informações da ação quando os preços mudam.

    Implemente dois tipos de observadores:

    • PriceAlert com um campo Threshold (float64)—quando atualizado, ele retorna ALERT: [stockName] at $[price] crossed threshold $[threshold] se o preço estiver acima do limite, ou WATCHING: [stockName] at $[price] (threshold: $[threshold]) se estiver abaixo
    • PriceLogger com um campo LogName (string)—quando atualizado, ele retorna [logName] recorded [stockName]: $[price]

    Ambos os observadores devem ter um método que retorne sua string de resposta formatada.

  • stock.go: Crie seu sujeito que gerencia os observadores e os notifica sobre mudanças de preço.

    Construa uma struct Stock com campos para Name (string), Price (float64) e um slice de observadores. Adicione estes métodos:

    • Subscribe(o Observer) para adicionar um observador
    • SetPrice(price float64) []string que atualiza o preço e notifica todos os observadores, retornando um slice com todas as suas respostas

    Também crie um construtor NewStock(name string, initialPrice float64) *Stock.

  • main.go: Configure sua ação com observadores e simule mudanças de preço.

    Leia o nome da ação e o preço inicial. Em seguida, leia a contagem de observadores a serem criados. Para cada observador, leia seu tipo (alert ou logger) e sua configuração (limite para alertas, nome do log para loggers). Inscreva cada observador na ação.

    Finalmente, leia um novo preço, atualize a ação e imprima a resposta de cada observador em sua própria linha.

As seguintes entradas serão fornecidas:

  • Linha 1: Nome da ação
  • Linha 2: Preço inicial
  • Linha 3: Número de observadores
  • Para cada observador: tipo (alert ou logger), seguido pelo valor de configuração
  • Linha final: Novo preço a ser definido

Formate todos os preços com duas casas decimais.

Por exemplo, dado:

GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50

Sua saída deve ser:

ALERT: GOOG at $160.50 crossed threshold $155.00
TradeLog recorded GOOG: $160.50
ALERT: GOOG at $160.50 crossed threshold $140.00

E dado:

AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25

Sua saída deve ser:

DailyLog recorded AAPL: $175.25
WATCHING: AAPL at $175.25 (threshold: $200.00)

Observe como o Stock não sabe se está notificando alertas ou loggers—ele apenas chama Update em cada observador. Este desacoplamento facilita a adição de novos tipos de observadores sem modificar o código do Stock!

Folha de consulta

O padrão Observer define um relacionamento de um para muitos onde um sujeito (subject) notifica múltiplos observadores (observers) automaticamente quando seu estado muda. Isso desacopla o sujeito de seus observadores, tornando-o ideal para sistemas de eventos e notificações.

Estrutura Básica

Defina uma interface de observador e um sujeito que gerencia as inscrições:

type Observer interface {
    Update(event string)
}

type Subject struct {
    observers []Observer
}

func (s *Subject) Subscribe(o Observer) {
    s.observers = append(s.observers, o)
}

func (s *Subject) Notify(event string) {
    for _, observer := range s.observers {
        observer.Update(event)
    }
}

Implementando Observadores

Qualquer tipo que implemente a interface Observer pode se inscrever:

type EmailAlert struct{ Address string }

func (e EmailAlert) Update(event string) {
    fmt.Printf("Email to %s: %s\n", e.Address, event)
}

type Logger struct{}

func (l Logger) Update(event string) {
    fmt.Println("Log:", event)
}

Uso

Inscreva os observadores e notifique-os sobre as mudanças:

subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})

subject.Notify("Order placed")
// Email to user@example.com: Order placed
// Log: Order placed

O sujeito notifica todos os observadores sem conhecer seus tipos concretos, facilitando a adição de novos tipos de observadores sem modificar o código existente.

Experimente você mesmo

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Ler o nome da ação
	stockName, _ := reader.ReadString('\n')
	stockName = strings.TrimSpace(stockName)

	// Ler o preço inicial
	initialPriceStr, _ := reader.ReadString('\n')
	initialPrice, _ := strconv.ParseFloat(strings.TrimSpace(initialPriceStr), 64)

	// Ler o número de observadores
	numObserversStr, _ := reader.ReadString('\n')
	numObservers, _ := strconv.Atoi(strings.TrimSpace(numObserversStr))

	// TODO: Criar uma nova Stock usando o construtor NewStock

	// TODO: Iterar por cada observador
	for i := 0; i < numObservers; i++ {
		// Ler o tipo de observador ("alert" ou "logger")
		observerType, _ := reader.ReadString('\n')
		observerType = strings.TrimSpace(observerType)

		// Ler o valor de configuração
		configValue, _ := reader.ReadString('\n')
		configValue = strings.TrimSpace(configValue)

		// TODO: Criar o observador apropriado com base no tipo
		// - Para "alert": converter configValue para o limite float64, criar PriceAlert
		// - Para "logger": usar configValue como LogName, criar PriceLogger
		// TODO: Inscrever o observador na stock
	}

	// Ler o novo preço
	newPriceStr, _ := reader.ReadString('\n')
	newPrice, _ := strconv.ParseFloat(strings.TrimSpace(newPriceStr), 64)

	// TODO: Definir o novo preço e obter as respostas

	// TODO: Imprimir cada resposta em sua própria linha
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos