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 placedEste 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.
Desafio
FácilVamos 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
Observercom um métodoUpdate(stockName string, price float64)que recebe informações da ação quando os preços mudam.Implemente dois tipos de observadores:
PriceAlertcom um campoThreshold(float64)—quando atualizado, ele retornaALERT: [stockName] at $[price] crossed threshold $[threshold]se o preço estiver acima do limite, ouWATCHING: [stockName] at $[price] (threshold: $[threshold])se estiver abaixoPriceLoggercom um campoLogName(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
Stockcom campos paraName(string),Price(float64) e um slice de observadores. Adicione estes métodos:Subscribe(o Observer)para adicionar um observadorSetPrice(price float64) []stringque 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 (
alertoulogger) 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 (
alertoulogger), 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.50Sua 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.00E dado:
AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25Sua 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 placedO 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
}
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
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica13Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool