Introducción a los patrones de diseño
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 85 de 107.
Patrones de diseño son soluciones reutilizables a problemas comunes que surgen en el diseño de software. No son código que se copia directamente, sino más bien plantillas que guían cómo estructurar tus tipos y sus interacciones para resolver desafíos específicos.
Los patrones de diseño se agrupan típicamente en tres categorías:
| Categoría | Propósito | Ejemplos |
|---|---|---|
| Creacionales | Mecanismos de creación de objetos | Singleton, Factory, Builder |
| Estructurales | Composición de tipos en estructuras más grandes | Adapter, Decorator, Composite |
| De comportamiento | Comunicación entre objetos | Observer, Strategy, Command |
En Go, los patrones de diseño se ven diferentes que en los lenguajes de POO tradicionales.
Sin classes ni herencia, Go implementa patrones utilizando interfaces, struct embedding y composición. Por ejemplo, donde Java podría usar una abstract class, Go usa una interface. Donde C++ podría usar herencia, Go usa embedding.
Aprender patrones te ayuda a reconocer problemas comunes y a comunicar soluciones de manera efectiva con otros desarrolladores. Cuando alguien menciona "usa el patrón Strategy aquí", todo el equipo entiende el enfoque sin necesidad de explicaciones extensas.
En las próximas lecciones, implementarás varios patrones fundamentales en Go, comenzando con patrones de creación como Singleton y Factory, para luego pasar a patrones de comportamiento como Observer y Strategy.
Desafío
Fácil¡Vamos a construir un sistema de notificación sencillo que demuestre cómo los patrones de diseño ayudan a organizar el código! Crearás una estructura que podría extenderse fácilmente con patrones como Observer o Strategy en futuras lecciones, pero por ahora, concéntrate en configurar interfaces y tipos limpios que sigan los buenos principios de la POO.
Organizarás tu código en tres archivos:
notifier.go: Define la interfaz principal y los tipos para tu sistema de notificación.Crea una interfaz
Notifiercon un único métodoSend(message string) stringque devuelva un mensaje de confirmación.Implementa dos tipos de notificadores concretos:
EmailNotifiercon un campoAddress: su métodoSenddevuelveEmail to [address]: [message]SMSNotifiercon un campoPhoneNumber: su métodoSenddevuelveSMS to [phone]: [message]
dispatcher.go: Crea un despachador que funcione con cualquier notificador a través de la interfaz.Construye una estructura
NotificationDispatcherque contenga un slice de interfacesNotifier. Añade estos métodos:AddNotifier(n Notifier)para registrar un notificadorBroadcast(message string) []stringque envíe el mensaje a través de todos los notificadores registrados y devuelva un slice con todas las cadenas de confirmación
También crea un constructor
NewNotificationDispatcher()que devuelva un despachador inicializado.main.go: Configura los notificadores y transmite los mensajes.Lee una cantidad de notificadores a crear. Para cada notificador, lee su tipo (
emailosms) y su destino (dirección o número de teléfono). Añade cada uno a un despachador.Luego lee un mensaje para transmitir. Envíalo a través de todos los notificadores e imprime cada confirmación en su propia línea.
Se proporcionarán las siguientes entradas:
- Línea 1: Número de notificadores
- Para cada notificador: tipo (
emailosms), luego el destino - Línea final: Mensaje a transmitir
Por ejemplo, dado:
3
email
alice@example.com
sms
555-1234
email
bob@example.com
Server maintenance at midnightTu salida debería ser:
Email to alice@example.com: Server maintenance at midnight
SMS to 555-1234: Server maintenance at midnight
Email to bob@example.com: Server maintenance at midnightY dado:
2
sms
555-9999
sms
555-0000
Meeting canceledTu salida debería ser:
SMS to 555-9999: Meeting canceled
SMS to 555-0000: Meeting canceledEsta estructura demuestra cómo las interfaces permiten diseños flexibles y extensibles. El despachador no conoce los tipos específicos de notificadores; solo sabe que pueden ejecutar Send. ¡Esta es la base sobre la cual se construyen patrones como Observer y Strategy!
Hoja de referencia
Los patrones de diseño son soluciones reutilizables a problemas comunes en el diseño de software. Son plantillas que guían cómo estructurar los tipos y sus interacciones.
Los patrones de diseño se agrupan en tres categorías:
| Categoría | Propósito | Ejemplos |
|---|---|---|
| Creacionales | Mecanismos de creación de objetos | Singleton, Factory, Builder |
| Estructurales | Composición de tipos en estructuras más grandes | Adapter, Decorator, Composite |
| De comportamiento | Comunicación entre objetos | Observer, Strategy, Command |
En Go, los patrones de diseño se implementan utilizando interfaces, incrustación de estructuras (struct embedding) y composición en lugar de clases y herencia. Donde los lenguajes de POO tradicionales utilizan clases abstractas, Go utiliza interfaces. Donde utilizan herencia, Go utiliza incrustación.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer el número de notificadores
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Crear un nuevo despachador
dispatcher := NewNotificationDispatcher()
// Leer cada notificador y añadirlo al despachador
for i := 0; i < count; i++ {
notifierType, _ := reader.ReadString('\n')
notifierType = strings.TrimSpace(notifierType)
destination, _ := reader.ReadString('\n')
destination = strings.TrimSpace(destination)
// TODO: Crear el notificador apropiado basado en el tipo
// y añadirlo al despachador
// Pista: Comprobar si notifierType es "email" o "sms"
}
// Leer el mensaje a difundir
message, _ := reader.ReadString('\n')
message = strings.TrimSpace(message)
// TODO: Difundir el mensaje e imprimir cada confirmación
}
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
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool