Menu
Coddy logo textTech

Patrón Observer

Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 89 de 107.

El patrón Observer define una relación de uno a muchos entre objetos. Cuando un objeto (el sujeto) cambia de estado, todos sus dependientes (observadores) son notificados automáticamente. Esto es ideal para sistemas de eventos, notificaciones o cualquier escenario donde múltiples componentes necesiten reaccionar a los cambios.

En Go, implementamos este patrón utilizando una interfaz para los observadores y una estructura de sujeto (subject) que gestiona las suscripciones:

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

Cualquier tipo que implemente la interfaz Observer puede suscribirse para recibir notificaciones:

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

El sujeto notifica a todos los observadores sin conocer sus tipos concretos:

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

subject.Notify("Order placed")
// Correo electrónico a user@example.com: Order placed
// Registro: Order placed

Este patrón desacopla el sujeto de sus observadores, lo que facilita la adición de nuevos tipos de observadores sin modificar el código existente. Se utiliza comúnmente en arquitecturas orientadas a eventos, frameworks de GUI y sistemas de mensajería pub/sub.

challenge icon

Desafío

Fácil

¡Vamos a construir un sistema de alerta de precios de acciones utilizando el patrón Observer! Crearás un sistema donde múltiples servicios de alerta reciben notificaciones automáticamente cada vez que cambia el precio de una acción, lo cual es perfecto para demostrar cómo los observadores pueden reaccionar a eventos sin que el sujeto conozca sus tipos concretos.

Organizarás tu código en tres archivos:

  • observer.go: Define tu interfaz de observador y los tipos de observadores concretos.

    Crea una interfaz Observer con un método Update(stockName string, price float64) que reciba información de la acción cuando los precios cambien.

    Implementa dos tipos de observadores:

    • PriceAlert con un campo Threshold (float64): cuando se actualiza, devuelve ALERT: [stockName] at $[price] crossed threshold $[threshold] si el precio está por encima del umbral, o WATCHING: [stockName] at $[price] (threshold: $[threshold]) si está por debajo.
    • PriceLogger con un campo LogName (string): cuando se actualiza, devuelve [logName] recorded [stockName]: $[price].

    Ambos observadores deben tener un método que devuelva su cadena de respuesta formateada.

  • stock.go: Crea tu sujeto que gestiona a los observadores y les notifica los cambios de precio.

    Construye un struct Stock con campos para Name (string), Price (float64) y un slice de observadores. Añade estos métodos:

    • Subscribe(o Observer) para añadir un observador.
    • SetPrice(price float64) []string que actualiza el precio y notifica a todos los observadores, devolviendo un slice con todas sus respuestas.

    También crea un constructor NewStock(name string, initialPrice float64) *Stock.

  • main.go: Configura tu acción con observadores y simula cambios de precio.

    Lee el nombre de la acción y el precio inicial. Luego lee un recuento de observadores a crear. Para cada observador, lee su tipo (alert o logger) y su configuración (umbral para alertas, nombre de registro para loggers). Suscribe cada observador a la acción.

    Finalmente, lee un nuevo precio, actualiza la acción e imprime la respuesta de cada observador en su propia línea.

Se proporcionarán las siguientes entradas:

  • Línea 1: Nombre de la acción
  • Línea 2: Precio inicial
  • Línea 3: Número de observadores
  • Para cada observador: tipo (alert o logger), luego el valor de configuración
  • Línea final: Nuevo precio a establecer

Formatea todos los precios con dos decimales.

Por ejemplo, dado:

GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50

Tu salida debería ser:

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

Y dado:

AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25

Tu salida debería ser:

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

Observa cómo el Stock no sabe si está notificando a alertas o a loggers; simplemente llama a Update en cada observador. ¡Este desacoplamiento facilita la adición de nuevos tipos de observadores sin modificar el código de Stock!

Hoja de referencia

El patrón Observer define una relación de uno a muchos donde un sujeto notifica a múltiples observadores automáticamente cuando su estado cambia. Esto desacopla al sujeto de sus observadores, lo que lo hace ideal para sistemas de eventos y notificaciones.

Estructura básica

Define una interfaz de observador y un sujeto que gestione las suscripciones:

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

Implementación de observadores

Cualquier tipo que implemente la interfaz Observer puede suscribirse:

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

Suscribe observadores y notifícales los cambios:

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

El sujeto notifica a todos los observadores sin conocer sus tipos concretos, lo que facilita la adición de nuevos tipos de observadores sin modificar el código existente.

Pruébalo tú mismo

package main

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

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

	// Leer el nombre de la acción
	stockName, _ := reader.ReadString('\n')
	stockName = strings.TrimSpace(stockName)

	// Leer el precio inicial
	initialPriceStr, _ := reader.ReadString('\n')
	initialPrice, _ := strconv.ParseFloat(strings.TrimSpace(initialPriceStr), 64)

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

	// TODO: Crear un nuevo Stock usando el constructor NewStock

	// TODO: Iterar a través de cada observador
	for i := 0; i < numObservers; i++ {
		// Leer el tipo de observador ("alert" o "logger")
		observerType, _ := reader.ReadString('\n')
		observerType = strings.TrimSpace(observerType)

		// Leer el valor de configuración
		configValue, _ := reader.ReadString('\n')
		configValue = strings.TrimSpace(configValue)

		// TODO: Crear el observador apropiado basado en el tipo
		// - Para "alert": convertir configValue a un umbral float64, crear PriceAlert
		// - Para "logger": usar configValue como LogName, crear PriceLogger
		// TODO: Suscribir el observador al stock
	}

	// Leer el nuevo precio
	newPriceStr, _ := reader.ReadString('\n')
	newPrice, _ := strconv.ParseFloat(strings.TrimSpace(newPriceStr), 64)

	// TODO: Establecer el nuevo precio y obtener las respuestas

	// TODO: Imprimir cada respuesta en su propia línea
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos