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) altera seu estado, todos os seus dependentes (observadores) são notificados automaticamente. Isso é ideal para sistemas de eventos, notificações ou qualquer cenário em que vários componentes precisem reagir a mudanças.

Em Go, implementamos esse padrão usando uma interface para observadores e uma struct de assunto 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 assunto notifica todos os observadores 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: Pedido realizado
// Log: Pedido realizado

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

challenge icon

Desafio

Fácil

Vamos criar um sistema de alerta de preços de ações usando o padrão Observer! Você criará um sistema no qual vários serviços de alerta recebem automaticamente notificações sempre que o preço de uma ação muda, perfeito para demonstrar como observadores podem reagir a eventos sem que o assunto conheça seus tipos concretos.

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

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

    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 observador:

    • PriceAlert com um campo Threshold (float64); quando atualizado, 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, retorna [logName] recorded [stockName]: $[price]

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

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

    Crie uma struct Stock com campos para Name (string), Price (float64) e uma 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 uma slice com todas as respostas deles

    Crie também 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 quantidade 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 registradores). Inscreva cada observador na ação.

    Por fim, 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), depois o valor de configuração
  • Linha final: Novo preço a ser definido

Formate todos os preços com duas casas decimais.

Por exemplo, considerando:

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 considerando:

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 registradores. Ele simplesmente chama Update em cada observador. Esse desacoplamento facilita a adição de novos tipos de observadores sem modificar o código do Stock!

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 um novo Stock usando o construtor NewStock

	// TODO: Percorrer 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": faça o parse de configValue como threshold float64, crie PriceAlert
		// - Para "logger": use configValue como LogName, crie PriceLogger
		// TODO: Inscrever o observador no stock
	}

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

	// TODO: Defina o novo preço e obtenha as respostas

	// TODO: Imprima 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

Pratique por conta própria: Compilador de Go online