GRASP: los principios de diseño que deberías conocer antes que SOLID

GRASP - General Responsibility Assignment Software Patterns - El padre de SOLID Cuando hablamos de GRASP, hablamos de la obra maestra que en 1997 escribió Craig Larman. SOLID es el resultado natural de aplicar GRASP. Pero, que es GRASP? Un conjunto de principios anterior, más completo y más potente que SOLID. GRASP proviene del acrónimo de General Responsibility Assignment Software Patterns.

Craig Larman no inventó nada, recopiló y formalizó patrones de asignación de responsabilidades que los buenos diseñadores de software ya aplicaban de forma intuitiva. Pocos años después, Robert C. Martin recopiló cinco principios de diseño orientado a objetos en su paper "Design Principles and Design Patterns" (2000), y Michael Feathers los bautizó con el acrónimo SOLID. Aunque GRASP y SOLID nacieron de forma independiente, GRASP fundamenta y explica por qué SOLID funciona cuando funciona.

SOLID se ha convertido en un dogma y los dogmas son muy peligrosos en ingeniería. SOLID te dice qué hacer, no te enseña a pensar. Es fundamental conocer que SOLID es la respuesta que escupe un conjunto de principios llamados GRASP.

GRASP es el que te da la base para entender por qué SOLID funciona cuando funciona.

Un desarrollador que solo conoce SOLID tiende a crear interfaces para todo "porque hay que depender de abstracciones", a separar clases obsesivamente "por la responsabilidad única" sin tener claro qué criterio usa para definir esa responsabilidad, y a aplicar patrones de forma mecánica sin preguntarse si el contexto lo justifica.

GRASP te da el criterio que SOLID no te da. Te enseña a hacerte las preguntas correctas antes de tomar decisiones de diseño.

Puedes descargar esta obra maestra en PDF más abajo.

Que frameworks aplican los principios de GRASP?

Spring (Java) y Symfony (PHP) son los que más fielmente implementan los nueve principios. ASP.NET Core aplica la mayoría de ellos de forma nativa: DI, middleware, interfaces estables, alta cohesión en sus módulos. Django aplica varios con buenos resultados, aunque su ORM Active Record le resta pureza en Low Coupling. NestJS podría aplicar todos, es el framework de JS que más se acerca.

Spring (Java) — Implementa los 9 principios. Contenedor IoC, factories, interfaces estables, módulos cohesivos
Symfony (PHP) — Implementa los 9 principios. Inspirado directamente en Spring, contenedor DI, EventDispatcher, componentes desacoplados
ASP.NET Core (C#) — Aplica la mayoría de forma nativa: DI, middleware pipeline, interfaces por todo el sistema de hosting
NestJS (Node/TS) — El más cercano en JavaScript. Módulos, DI, guards, interceptors, pipes
Django (Python) — Aplica varios principios, pero su ORM Active Record compromete Low Coupling
Laravel / Rails / Express — Priorizan velocidad de entrega sobre principios de diseño

El patrón que se repite es claro: los frameworks que nacen del mundo enterprise (Spring, Symfony, ASP.NET Core, NestJS) tienden a aplicar GRASP porque se enfrentan a proyectos grandes y modulares donde la robustez, seguridad y escalabilidad deben ser troncales.

Los que nacen del mundo startup/prototipado (Rails, Laravel, Express) priorizan velocidad de entrega y sacrifican principios de diseño a cambio de productividad inmediata.

Ninguno pone "GRASP" en su landing page, pero cuando lees su código fuente, se nota quién ha hecho los deberes y quién no.

Vamos al grano: los 9 principios de GRASP

1. Information Expert (Experto en información)

Quien tiene los datos necesarios para hacer esto?

Asigna cada responsabilidad a la clase que ya tiene la información para cumplirla. Parece de sentido común, y lo es, pero se viola constantemente.

Un ejemplo cotidiano: tienes una clase Pedido con sus líneas de productos, cantidades y precios. Quien debería calcular el total? El propio Pedido, porque tiene todos los datos. No un PedidoService, no un CalculadoraDePrecios, no un helper suelto.

Cuando ves lógica de negocio repartida por servicios que acceden a las propiedades de un objeto para tomar decisiones sobre él, estás ante una violación de Information Expert. Esa lógica debería vivir en el objeto que posee los datos.

2. Creator (Creador)

Quien debería crear este objeto?

Un objeto A debería crear un objeto B cuando A contiene a B, A usa B directamente, o A tiene los datos necesarios para inicializar B.

No confundir con el contenedor DI de Symfony

Creator responde a qué clase tiene la responsabilidad lógica de instanciar un objeto de dominio. El contenedor DI es un mecanismo técnico que resuelve dependencias entre servicios. Son capas diferentes del mismo diseño.

Si tu sistema genera facturas a partir de pedidos, quien crea la factura? Quien tiene toda la información del pedido y sus líneas. No un controller, no un endpoint de API. La responsabilidad de creación va donde están los datos y el contexto.

Esto no significa que no puedas usar factories. Significa que la factory debería existir cuando hay una razón concreta para ello (lógica de creación compleja, múltiples variantes), no por defecto.

3. Controller (Controlador)

Quien coordina este caso de uso?

Ojo: no es el controller de MVC. Es el objeto que recibe una petición del sistema y coordina el flujo de trabajo, delegando cada paso a quien corresponde.

Piensa en un director de orquesta. No toca ningún instrumento, pero sabe cuándo entra cada sección. Un controller GRASP recibe la solicitud "actualizar usuario", comprueba permisos (delegando al experto), valida datos (delegando al validador), persiste cambios (delegando al repositorio) y registra la acción (delegando al auditor). No contiene lógica de negocio propia.

4. Low Coupling (Bajo acoplamiento)

Cuántas dependencias tiene esta clase? Son todas necesarias?

Cuantas menos conexiones directas existan entre clases, más fácil será modificar, testear y reutilizar cada una. Esto no es novedad, pero GRASP lo plantea como un criterio de evaluación constante, no como un objetivo abstracto.

Un módulo de facturación que depende directamente de la librería de envío de emails, de la conexión a base de datos concreta y del sistema de caché tiene un acoplamiento alto. Si depende de interfaces que abstraen esas tres cosas, el acoplamiento baja drásticamente.

Ahora sí, esto es el contenedor DI de Symfony

Antes del contenedor DI de Symfony, si A dependía de B y a su vez B dependía de C, se producía un fuerte acoplamiento porque en el caso de que A o B cambiaran, el problema se propagaba en cascada. Por ejemplo: new A() depende de new B(), que a su vez depende de new C(). Cada new es un punto de acoplamiento directo.

El contenedor DI resuelve esto: tú declaras qué interfaces necesita cada clase y el contenedor monta todo el árbol de dependencias automáticamente, sin que ninguna clase conozca las implementaciones concretas de las que depende.

5. High Cohesion (Alta cohesión)

Todo lo que hace esta clase está relacionado entre sí?

Alta cohesión significa que una clase tiene un conjunto reducido de responsabilidades estrechamente vinculadas. Es el complemento directo de bajo acoplamiento: si desacoplas sin criterio, acabas con clases que hacen demasiadas cosas inconexas.

El antipatrón clásico es la clase AdminService o Utils que acumula métodos sin relación entre sí: validar usuarios, enviar correos, generar informes y exportar CSV. Cada una de esas responsabilidades debería estar en su propia clase cohesiva.

6. Polymorphism (Polimorfismo)

Estoy usando condicionales para decidir comportamientos que varían según el tipo?

Cuando tienes un switch o una cadena de if/else que elige comportamiento en función de un tipo o una categoría, eso debería resolverse con polimorfismo: una interfaz común y múltiples implementaciones.

Por ejemplo, un sistema de notificaciones con envío por email, SMS y push. En vez de un método con tres bloques condicionales, defines una interfaz Notificador y tres clases que la implementan. Añadir un cuarto canal (Telegram, Slack, lo que sea) no requiere tocar código existente.

7. Pure Fabrication (Fabricación pura)

Ninguna clase de mi dominio encaja para esta responsabilidad?

A veces necesitas una clase que no representa nada del mundo real ni del dominio del problema. No es un usuario, ni un pedido ni un producto. Es un artefacto técnico que existe porque tu arquitectura lo necesita.

Repositorios, loggers, gestores de sesión, serializadores, despachadores. Ninguno modela una entidad real. Son fabricaciones puras, y GRASP los reconoce explícitamente como legítimos.

Importante

Nunca intentes forzar estas responsabilidades dentro de tus entidades de dominio.

8. Indirection (Indirección)

Necesito un intermediario para que estos dos componentes no se conozcan directamente?

GRASP es mejor que SOLID - Principios de diseño de software

Meter una capa intermedia entre dos componentes para evitar acoplamiento directo. Middleware, buses de mensajes, despachadores de eventos, adaptadores. Todos son formas de indirección.

Tu controlador HTTP no debería hablar directamente con la base de datos. Tu caso de uso no debería saber si la notificación se envía por correo o por Slack. Un intermediario absorbe esa dependencia y desacopla ambas partes.

Cada capa de indirección añade complejidad al flujo, no debes abusar. Usa indirección cuando el acoplamiento que eliminas justifica la complejidad que añades.

Por ejemplo; Cuando diseñamos un sistema de facturación. Ventas no sabe ni quiere saber cómo se genera una factura. Solo dice "oye, factura esto" y se desentiende. El servicio de facturación recibe el encargo, calcula impuestos, numera, guarda y manda lo que haya que mandar. Si mañana cambian las reglas fiscales o se migra la base de datos, ventas sigue haciendo lo mismo de siempre. Cada uno a lo suyo.

9. Protected Variations (Variaciones protegidas)

Qué partes de mi sistema van a cambiar? Están blindadas tras una interfaz estable?

Identifica los puntos de inestabilidad o variación en tu diseño y protégelos con interfaces. Si sabes que la pasarela de pago puede cambiar, que el proveedor de almacenamiento puede migrar, o que el formato de exportación puede ampliarse, esos puntos necesitan una interfaz estable por delante.

Esto no significa poner interfaces a todo. Significa analizar tu sistema, identificar qué es probable que cambie, y proteger esos puntos concretos. Lo que no va a cambiar no necesita abstracción adicional.

Esto no es modularidad

Puedes tener un sistema perfectamente modular y que aun así se rompa en cascada cuando cambia la pasarela de pago, porque aunque estaba en su propio módulo, veinte clases dependían directamente de la implementación concreta de Stripe sin una interfaz por delante.

Protected Variations te dice "de todo lo que tienes, averigua qué va a cambiar y protege eso". Uno es estructura, el otro es criterio.

Conclusión

SOLID te da cinco reglas. GRASP te da un modelo de pensamiento. Un desarrollador que entiende GRASP aplica SOLID de forma natural, porque entiende los fundamentos que hay debajo. Al revés no funciona igual: puedes memorizar SOLID y seguir tomando malas decisiones de diseño porque te falta el marco de razonamiento.

La diferencia es la misma que existe entre seguir una receta y saber cocinar.