Un patrón de diseño es una solución nombrada y reusable a un problema de diseño recurrente. El libro del Gang of Four catalogó veintitrés en 1994, pero el valor hoy no es el catálogo — es el vocabulario compartido que permite que un ingeniero diga “esto es un Strategy” y otro imagine inmediatamente la forma del código.
Los patrones se vuelven dogmáticos solo cuando se aplican sin contexto. La visión moderna: aprende los pocos que aparecen en todo codebase, reconoce sus costos y recurra a ellos cuando el problema realmente encaje — nunca por defecto.
Los cuatro que te encuentras en todos lados
| Patrón | Problema que resuelve | Forma moderna |
|---|---|---|
| Strategy | Elegir un algoritmo en runtime sin escaleras de if/else | Una función pasada como argumento; una interfaz “callable” |
| Observer | Notificar a N suscriptores cuando algo cambia | Event emitters, streams reactivos, callbacks subscribe |
| Decorator | Envolver un objeto para añadir comportamiento sin subclasear | Cadenas de middleware (Express, Rack), sintaxis de decorador (Python) |
| Factory | Desacoplar qué se construye de cómo se construye | Una función builder; contenedores de inyección de dependencias |
Cuatro casos cubren el noventa por ciento de las referencias a patrones en código de producción. Interioriza estos y la mayoría de conversaciones sobre “patrones” fluyen; las entradas restantes del GoF (Visitor, Command, Iterator) son útiles pero más raras.
Composición vs herencia
El único principio que ha desplazado más uso de patrones es composición sobre herencia. Una clase que tiene-un helper puede intercambiar ese helper en runtime; una clase que es-un padre está soldada a la interfaz del padre de por vida.
Herencia Composición
class Duck extends Bird class Duck { flight: Flyer; quack: Quacker }
— vuelo horneado — vuelo intercambiable (FlyWithWings → NoFly)
— graznido integrado — graznido intercambiable por separado
La herencia sigue siendo la herramienta correcta cuando las subclases realmente sustituyen al padre (la regla de Liskov). La composición gana cuando el comportamiento es ortogonal al tipo.
Alternativas funcionales
Varios patrones “clásicos” se reducen a una única función en un lenguaje con funciones de primera clase:
| Patrón clásico | Reemplazo funcional |
|---|---|
| Strategy | Pasa una función de orden superior |
| Template Method | Pasa callbacks a una función genérica |
| Command | Un literal { run, undo } — o simplemente la función |
| Iterator | Cualquier iterable / generator (yield) |
| Observer | Una lista de callbacks; subscribe añade uno |
Si el lenguaje soporta closures y genéricos, recurrir a un Strategy de interfaz-y-clase es una señal de que el patrón fue cargo-culted. El objetivo es el comportamiento, no la forma — los closures son la forma más barata.
Antipatrones a evitar
- God Object: una clase “para mantenerlo simple” que acumula 50+ métodos. Divide por responsabilidad; los nombres quedan más claros.
- Singletons prematuros: el estado de instancia única se vuelve un global — acoplamiento invisible, no testeable, cada test hereda el estado de cada otro test.
- Patrón solo de nombre: una “Factory” que tiene un producto, un “Strategy” con una estrategia, un “Observer” con cero suscriptores. Cada uno fue una excusa para usar un patrón; cada uno debería eliminarse.
- Herencia muñeca rusa:
A → B → C → D → E, donde cambiarBes un campo minado. La composición aplana la cadena.
Cuándo los patrones dañan
Un patrón es correcto cuando aparece la tercera instancia del problema — no la primera. La regla de tres:
- Primera aparición: escribe el código inline. YAGNI (You Aren’t Gonna Need It) aplica.
- Segunda aparición: copiar-pegar es más rápido que la abstracción equivocada.
- Tercera aparición: ahora la forma es real; el patrón (o su equivalente funcional) está justificado.
La abstracción prematura es más cara que la duplicación, porque las abstracciones se osifican: una vez que tres llamadores dependen de la forma equivocada, cada refactor debe satisfacer a los tres.
Trayectoria de práctica
- Elige cualquier codebase que conozcas; encuentra un Strategy y un Observer. Describe el problema que resuelve cada uno en una frase.
- Refactoriza una cadena de
if/elsede 4 ramas a unMap<string, Strategy>y observa cómo el sistema de tipos documenta ahora los casos. - Reemplaza un Strategy basado en clase por una función de orden superior y anota qué se simplifica (sin interfaz, sin ceremonia) y qué se pierde (sin dónde poner una doc-comment de interfaz).
- Audita un codebase buscando “patrones solo de nombre” — factories con un producto, singletons sin estado compartido, clases abstractas con una sola subclase concreta — y elimina uno.
- Elige una entrada del GoF que nunca hayas usado (Visitor, Memento, Chain of Responsibility) y escribe un párrafo: qué problema resuelve, cuándo es la herramienta equivocada y una alternativa moderna.
Cuándo es la herramienta correcta
| Situación | Conclusión |
|---|---|
| Tres o más casos de la misma forma | Un patrón (o su forma funcional) ya está justificado |
| Revisando código desconocido | Los nombres de patrones son el diccionario; apréndelos para leer, no para escribir |
| Eligiendo un lenguaje | Los closures/genéricos nativos te permiten expresar la mayoría de patrones como código plano |
| Diseñando la API de una librería | Prefiere composición + una interfaz pequeña; reserva la herencia para subtipos reales |
Un patrón es una herramienta para comunicar y estructurar, nunca un checklist para una promoción.