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, todas sus dependencias (observadores) reciben una notificación automáticamente. Esto es ideal para sistemas de eventos, notificaciones o cualquier escenario en el que varios componentes deban reaccionar a los cambios.

En Go, implementamos este patrón utilizando una interfaz para los observadores y una estructura subject que administra 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 a user@example.com: Order placed
// Log: Order placed

Este patrón desacopla el sujeto de sus observadores, lo que facilita agregar nuevos tipos de observadores sin modificar el código existente. Se usa habitualmente en arquitecturas controladas por eventos, frameworks de GUI y sistemas de mensajería pub/sub.

challenge icon

Desafío

Fácil

¡Construyamos un sistema de alertas de precios de acciones usando el patrón Observer! Crearás un sistema en el que varios servicios de alertas reciben automáticamente notificaciones cada vez que cambia el precio de una acción, 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 concretos de observadores.

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

    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 administra los observadores y les notifica los cambios de precio.

    Construye una estructura 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

    Crea también 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. Después, lee la cantidad de observadores que se crearán. Para cada observador, lee su tipo (alert o logger) y su configuración (el umbral para las alertas, el nombre de registro para los registradores). 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), después el valor de configuración
  • Línea final: Nuevo precio que se establecerá

Formatea todos los precios con dos decimales.

Por ejemplo, dada la siguiente entrada:

GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50

Tu salida debe 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 dada la siguiente entrada:

AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25

Tu salida debe ser:

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

Observa cómo Stock no sabe si está notificando a alertas o a registradores. Simplemente llama a Update en cada observador. ¡Este desacoplamiento facilita añadir nuevos tipos de observadores sin modificar el código de Stock!

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: Recorrer 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 según el tipo
		// - Para "alert": analizar configValue como 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: Establece el nuevo precio y obtén las respuestas

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

Practica por tu cuenta: Compilador de Go online