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 realizadoEste 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.
Desafio
FácilVamos 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
Observercom um métodoUpdate(stockName string, price float64)que recebe informações da ação quando os preços mudam.Implemente dois tipos de observador:
PriceAlertcom um campoThreshold(float64); quando atualizado, 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, 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
Stockcom campos paraName(string),Price(float64) e uma slice de observadores. Adicione estes métodos:Subscribe(o Observer)para adicionar um observadorSetPrice(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 (
alertoulogger) 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 (
alertoulogger), 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.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 considerando:
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 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
}
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 ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa 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 Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online