cv.cm

Programación · Patrones de diseño

Patrones de diseño explicados: los 23 patrones de la Banda de los Cuatro

8 min de lectura

Respuesta corta

Un patrón de diseño es una solución con nombre y reutilizable a un problema que aparece una y otra vez en el diseño de software, descrita a un nivel superior al del código concreto. El catálogo clásico es el libro de 1994 Design Patterns: Elements of Reusable Object-Oriented Software, de Gamma, Helm, Johnson y Vlissides (la «Banda de los Cuatro», o GoF), que recoge 23 patrones en tres grupos: 5 creacionales, 7 estructurales y 11 de comportamiento. Los patrones dan a los equipos un vocabulario común, pero son herramientas para problemas concretos, no objetivos en sí mismos.

Qué es un patrón de diseño

Un patrón de diseño no es una biblioteca ni un trozo de código que se copia. Es la descripción de un problema recurrente, el núcleo de una solución y las contrapartidas de usarla. El código lo sigues escribiendo tú, adaptado a tu lenguaje y a tu programa.

El libro GoF describe cada patrón con cuatro elementos esenciales:

  • Nombre: una o dos palabras que permiten a los desarrolladores hablar del diseño, como «podemos envolverlo en un patrón Decorator».
  • Problema: cuándo aplicar el patrón y la situación que lo hace necesario.
  • Solución: las clases u objetos implicados, sus responsabilidades y cómo colaboran.
  • Consecuencias: los costes y beneficios, como más flexibilidad a cambio de más clases.

La idea viene de la arquitectura. El libro de Christopher Alexander A Pattern Language (1977) describía soluciones recurrentes para edificios y ciudades, y los autores de GoF aplicaron la misma idea al software orientado a objetos.

El libro de la Banda de los Cuatro

Design Patterns: Elements of Reusable Object-Oriented Software fue publicado por Addison-Wesley en 1994. Sus ejemplos están en C++ y Smalltalk, pero los patrones se aplican a cualquier lenguaje orientado a objetos, incluidos Java, C#, TypeScript, Python y Kotlin.

Además del catálogo, el libro defiende dos principios generales que recorren la mayoría de los patrones:

  • Programa para una interfaz, no para una implementación. El código debe depender de lo que un objeto puede hacer, no de su clase concreta.
  • Prefiere la composición de objetos a la herencia de clases. Construir el comportamiento combinando objetos suele ser más flexible que construir jerarquías de clases profundas.

El libro también clasifica cada patrón por su ámbito: los patrones de clase usan herencia y quedan fijados en tiempo de compilación (patrón Factory Method, patrón Adapter de clase, patrón Interpreter, patrón Template Method), mientras que los patrones de objeto usan composición y pueden cambiar en tiempo de ejecución (todos los demás).

Los 23 patrones GoF por categoría

Los patrones creacionales tratan de cómo se crean los objetos. Los patrones estructurales tratan de cómo se combinan clases y objetos en estructuras mayores. Los patrones de comportamiento tratan de cómo se comunican los objetos y comparten responsabilidades.

PatrónCategoríaPropósito en una línea
Abstract FactoryCreacionalCrear familias de objetos relacionados sin nombrar sus clases concretas.
BuilderCreacionalConstruir un objeto complejo paso a paso, separando la construcción de la representación.
Factory MethodCreacionalDejar que las subclases decidan qué clase instanciar.
PrototypeCreacionalCrear objetos nuevos copiando una instancia existente.
SingletonCreacionalGarantizar que una clase tenga una sola instancia y dar acceso global a ella.
AdapterEstructuralConvertir una interfaz en otra que los clientes esperan.
BridgeEstructuralSeparar una abstracción de su implementación para que ambas puedan variar de forma independiente.
CompositeEstructuralTratar de la misma manera los objetos individuales y los árboles de objetos.
DecoratorEstructuralAñadir responsabilidades a un objeto de forma dinámica envolviéndolo.
FacadeEstructuralOfrecer una interfaz simple a un subsistema complejo.
FlyweightEstructuralCompartir el estado común para admitir de forma eficiente un gran número de objetos pequeños.
ProxyEstructuralOfrecer un sustituto que controla el acceso a otro objeto.
Chain of ResponsibilityDe comportamientoPasar una petición por una cadena de manejadores hasta que uno la atienda.
CommandDe comportamientoConvertir una petición en un objeto, para poder ponerla en cola, registrarla o deshacerla.
InterpreterDe comportamientoRepresentar la gramática de un lenguaje sencillo como clases y evaluar frases en él.
IteratorDe comportamientoAcceder a los elementos de una colección en orden sin exponer su estructura interna.
MediatorDe comportamientoCentralizar la comunicación entre objetos para que no se refieran unos a otros directamente.
MementoDe comportamientoCapturar y restaurar el estado de un objeto sin romper el encapsulamiento.
ObserverDe comportamientoNotificar automáticamente a todos los dependientes cuando un objeto cambia.
StateDe comportamientoDejar que un objeto cambie su comportamiento cuando cambia su estado interno.
StrategyDe comportamientoHacer intercambiable una familia de algoritmos detrás de una sola interfaz.
Template MethodDe comportamientoDefinir el esqueleto de un algoritmo y dejar que las subclases completen algunos pasos.
VisitorDe comportamientoAñadir operaciones nuevas a una jerarquía de clases sin cambiar esas clases.

Un patrón en la práctica: patrón Strategy en TypeScript

Aquí tienes un pequeño ejemplo de un patrón en acción. Un proceso de compra debe calcular el envío de distintas maneras. En lugar de una cadena if/else cada vez más larga, cada regla se convierte en un objeto intercambiable detrás de una sola interfaz:

interface ShippingStrategy {
  cost(weightKg: number): number;
}
class FlatRate implements ShippingStrategy {
  cost(): number { return 5; }
}
class PerKilogram implements ShippingStrategy {
  cost(weightKg: number): number { return 2 * weightKg; }
}
class Checkout {
  constructor(private shipping: ShippingStrategy) {}
  setShipping(s: ShippingStrategy): void { this.shipping = s; }
  total(itemsPrice: number, weightKg: number): number {
    return itemsPrice + this.shipping.cost(weightKg);
  }
}
const checkout = new Checkout(new FlatRate());
console.log(checkout.total(40, 3)); // 45
checkout.setShipping(new PerKilogram());
console.log(checkout.total(40, 3)); // 46

En TypeScript las funciones son valores, así que el mismo diseño puede escribirse con menos código. Es un tema frecuente: muchos patrones se simplifican, o desaparecen, en lenguajes con funciones de primera clase.

type Shipping = (weightKg: number) => number;
const flatRate: Shipping = () => 5;
const perKilogram: Shipping = (w) => 2 * w;
function total(itemsPrice: number, weightKg: number, shipping: Shipping): number {
  return itemsPrice + shipping(weightKg);
}
console.log(total(40, 3, flatRate));    // 45
console.log(total(40, 3, perKilogram)); // 46

Cómo elegir un patrón

Empieza por el problema, nunca por el patrón. Un patrón solo merece sus clases adicionales cuando elimina un dolor real en tu código. Estas preguntas apuntan a los candidatos habituales:

Si tu problema es...Considera
El código no debe depender de qué clase concreta creaFactory Method, Abstract Factory
Un constructor tiene muchos parámetros opcionalesBuilder
Necesitas adaptar una clase existente a otra interfazAdapter
Una biblioteca compleja necesita un único punto de entrada sencilloFacade
Quieres añadir funciones a los objetos sin una explosión de subclasesDecorator
Trabajas con árboles, como menús, archivos o widgets de interfazComposite, Visitor, Iterator
Muchas partes deben reaccionar cuando cambia un objetoObserver
El comportamiento depende de un modo o estado con muchas ramasState
Necesitas algoritmos o reglas intercambiablesStrategy
Las acciones deben poder deshacerse, ponerse en cola o registrarseCommand, Memento
  1. Escribe primero el código más simple que funcione.
  2. Cuando el mismo cambio siga doliendo (por ejemplo, cada nuevo tipo de pago modifica cinco archivos), ponle nombre al problema.
  3. Busca los patrones que abordan ese problema y lee sus consecuencias.
  4. Aplica la versión más pequeña del patrón que elimine el dolor, y detente ahí.

Críticas y límites

Los patrones de diseño son útiles, pero a menudo se usan mal. Conviene conocer las críticas más habituales:

  • Abuso: añadir fábricas, interfaces y gestores «por si acaso» hace que el código simple sea difícil de leer. A veces se llama fiebre de patrones.
  • Funciones que faltan en el lenguaje: en 1996 Peter Norvig demostró que 16 de los 23 patrones GoF son invisibles o más simples en lenguajes dinámicos como Lisp y Dylan. Las funciones de primera clase sustituyen a Strategy y Command, y los iteradores incorporados sustituyen a Iterator.
  • En particular, el patrón Singleton se considera ampliamente un antipatrón cuando se usa como estado global, porque oculta dependencias y dificulta las pruebas.
  • El catálogo es de 1994. La práctica posterior añadió otros patrones muy conocidos que no están en el libro, como la inyección de dependencias, el repositorio y las variantes de Model-View-Controller.

El valor duradero del libro es el vocabulario y el hábito de pensar en las contrapartidas. Decir «esto es un patrón Adapter» le dice mucho a otro desarrollador en pocas palabras.

Principios relacionados

Los patrones suelen explicarse junto con los principios SOLID del diseño orientado a objetos. Por ejemplo, el patrón Strategy y el patrón Decorator son formas directas de seguir el principio abierto/cerrado, y el patrón Abstract Factory es una forma común de aplicar el principio de inversión de dependencias. Aprender los principios te ayuda a entender por qué un patrón tiene la forma que tiene.

Preguntas frecuentes

¿Cuántos patrones de diseño hay?

El libro de la Banda de los Cuatro cataloga 23 patrones: 5 creacionales, 7 estructurales y 11 de comportamiento. Desde entonces se han descrito muchos otros patrones, por ejemplo para concurrencia, aplicaciones empresariales e interfaces de usuario.

¿Qué patrones de diseño debería aprender primero?

Empieza por los que más te encontrarás en código real: patrón Strategy, patrón Observer, patrón Factory Method, patrón Decorator, patrón Adapter y patrón Facade. Después aprende el patrón Builder, el patrón Composite y el patrón Command.

¿Los patrones de diseño siguen siendo relevantes?

Sí, como vocabulario común y como soluciones a problemas recurrentes. Algunos patrones ya están integrados en lenguajes y frameworks, así que los usas sin escribir la estructura completa de clases.

¿Los patrones de diseño solo se aplican a lenguajes orientados a objetos?

Los patrones GoF se escribieron para lenguajes orientados a objetos, pero las ideas de fondo aparecen en todas partes. En los lenguajes funcionales, muchos de ellos se convierten en simples funciones de orden superior.

¿Cuál es la diferencia entre un patrón de diseño y un algoritmo?

Un algoritmo es una secuencia precisa de pasos que resuelve un problema computacional, como ordenar. Un patrón de diseño describe cómo organizar el código y las responsabilidades, y se adapta a cada programa en lugar de seguirse paso a paso.

Todos los tutoriales de programación