Introducción: reglas componibles
El patrón Specification propone encapsular reglas de negocio dentro de objetos reutilizables y combinables. En lugar de escribir condiciones dispersas en distintos métodos, las reglas se expresan como especificaciones que pueden evaluarse sobre un objeto de dominio. Esto crea un lenguaje de negocio más claro y explícito, y permite que la lógica se componga con operadores como AND, OR y NOT.
La necesidad del patrón surge cuando las reglas empiezan a crecer y a repetirse. En sistemas reales, las decisiones de negocio suelen depender de múltiples condiciones: estados, rangos, dependencias y excepciones. Cuando estas condiciones se escriben como `if` dispersos, el código se vuelve difícil de mantener, porque no hay un punto central donde se defina la regla. Specification soluciona esto al convertir cada regla en un objeto que se puede testear, documentar y reutilizar.
Una especificación se puede definir como una función `IsSatisfiedBy(entity)`, que retorna verdadero o falso según si el objeto cumple la regla. Lo poderoso es la capacidad de composición: se pueden combinar especificaciones de forma declarativa. Por ejemplo, “cliente activo” AND “cliente con crédito” crea una nueva regla sin necesidad de reescribir la lógica. Esta composición permite expresar reglas complejas de forma legible y modular.
Otro beneficio es la separación entre reglas y consultas. En algunos enfoques, las especificaciones pueden traducirse a expresiones para consultas en bases de datos. Esto permite usar la misma definición de regla tanto en memoria como en consultas, evitando inconsistencias entre la lógica de aplicación y la lógica de persistencia. Aunque esta capacidad requiere un diseño cuidadoso, muestra el potencial del patrón para mantener coherencia en todo el sistema.
Specification también favorece la testabilidad. Cada regla se prueba en aislamiento, con entradas controladas. Esto es mucho más simple que probar un bloque de `if` que mezcla reglas. Además, se puede documentar cada especificación con un nombre significativo, lo que mejora la comunicación entre equipos técnicos y de negocio. En DDD, este patrón se usa para reflejar reglas del dominio de manera explícita.
Sin embargo, como todo patrón, debe usarse con criterio. Para reglas simples y pocas, puede ser un exceso. Pero cuando las reglas crecen y se combinan, Specification se vuelve una herramienta esencial para mantener claridad y consistencia. En resumen, el patrón permite expresar reglas de negocio de forma modular, composable y mantenible, lo que mejora la calidad del código y la capacidad de evolución del sistema.

Prompt: filter criteria cards, minimal style.
1. Naturaleza: filtros
La analogía del filtro es útil porque resume la esencia del patrón: separar elementos según criterios definidos. Un colador no cambia los elementos, solo decide cuáles pasan y cuáles no, basándose en una regla simple: el tamaño. En Specification ocurre lo mismo: la especificación no transforma el objeto, solo evalúa si cumple una condición. Esta separación de evaluación y transformación es clave para mantener reglas claras.
En la naturaleza, los filtros aparecen en múltiples escalas. Por ejemplo, los riñones filtran la sangre eliminando sustancias que cumplen ciertos criterios. No necesitan cambiar la composición de la sangre para decidir; solo aplican reglas de selección. En el contexto del patrón, la “sangre” sería el conjunto de entidades del dominio, y la especificación sería el criterio de filtrado. Esta analogía ilustra por qué el patrón es tan efectivo para manejar reglas complejas sin mezclar responsabilidades.
Otro ejemplo natural es la selección de alimento. Algunos animales filtran partículas de agua o aire y retienen solo lo que es útil. La regla no está dispersa; está encapsulada en el filtro. En software, esa encapsulación es la especificación. En lugar de repartir condiciones en diferentes lugares, la regla se concentra en un objeto que decide si algo “pasa” o no. Esto mejora la coherencia y facilita la reutilización.
Los filtros también son composables. En un proceso de filtrado real, se pueden aplicar múltiples filtros en secuencia: uno elimina partículas grandes, otro elimina contaminantes finos. Esa composición es equivalente a combinar especificaciones con AND o OR. Cada filtro hace una tarea concreta, y el resultado final es la combinación de reglas. La analogía ayuda a entender que el patrón no se limita a una regla aislada, sino que permite construir reglas complejas como una cadena de filtros simples.
También existe la idea de filtros con criterios diferentes según el contexto. En un sistema de riego, el filtro puede cambiar según la calidad del agua. En software, la especificación puede cambiar según el contexto del negocio o del usuario. El patrón permite encapsular esas variaciones sin mezclar la lógica en el flujo principal. Cada especificación representa un criterio explícito que puede intercambiarse según la necesidad.
En conclusión, la analogía del filtro demuestra que Specification es un mecanismo de selección basado en reglas claras. Encapsular reglas en objetos es equivalente a diseñar filtros especializados. Esta comparación ayuda a entender la modularidad y la composabilidad del patrón, y por qué es útil cuando las reglas se vuelven múltiples y complejas.

Prompt: sieve filtering, soft illustration.
2. Mundo Real: elegibilidad
En sistemas de negocio, el patrón Specification se usa con frecuencia para determinar elegibilidad. Un ejemplo claro es la evaluación de crédito: la decisión depende de múltiples reglas, como historial financiero, ingresos, comportamiento de pago y límites internos. Cada regla puede expresarse como una especificación. Luego, se combinan para determinar si el cliente es elegible. Esto produce una lógica clara y fácil de mantener.
Otro escenario es el de promociones en e-commerce. Para aplicar un descuento, se puede requerir que el cliente sea premium, que el pedido supere cierto monto y que el producto pertenezca a una categoría específica. Si estas condiciones se escriben dispersas en el código, es difícil mantenerlas y ajustarlas. Con Specification, cada regla es un objeto: `IsPremiumCustomer`, `OrderAboveAmount`, `IsInCategory`. La promoción se activa con una composición de estas reglas. El resultado es una lógica declarativa y reutilizable.
En sistemas de RRHH, las especificaciones pueden usarse para determinar elegibilidad de beneficios. Por ejemplo, un empleado puede ser elegible si tiene más de cierto tiempo en la empresa, pertenece a un área específica y tiene un contrato activo. Cada criterio se encapsula y se combina. Esto permite que las reglas evolucionen sin reescribir múltiples puntos del sistema. También facilita la auditoría: se puede explicar claramente por qué alguien es o no elegible.
En el sector salud, se pueden usar especificaciones para definir criterios de selección en estudios o tratamientos. Aunque el software no decide directamente sobre los pacientes, puede filtrar registros según reglas explícitas. El patrón permite expresar esos criterios de forma clara y verificable. Esto es valioso en contextos regulados, donde las reglas deben ser transparentes y auditables.
En logística, especificaciones pueden definir la elegibilidad de rutas o transportes. Por ejemplo, un envío puede requerir transporte refrigerado si el producto es perecedero y si la distancia supera cierto umbral. Las reglas se combinan para decidir el tipo de transporte. El patrón facilita mantener estas reglas en un contexto donde cambian con frecuencia por costos, regulaciones o acuerdos comerciales.
En resumen, el patrón se aplica en cualquier dominio donde existan reglas de selección o elegibilidad complejas. Su valor está en hacer explícitas esas reglas, permitir su combinación y facilitar cambios. En el mundo real, esto no solo mejora el código, sino también la capacidad de explicar decisiones de negocio y de ajustar la lógica con menor riesgo.

Prompt: eligibility rules checklist, flat infographic.
3. Implementación en C#: Código Paso a Paso
var spec = new IsActive().And(new HasCredit());
if (spec.IsSatisfiedBy(customer)) ...
En C#, la implementación de Specification suele comenzar con una interfaz simple, por ejemplo `ISpecification<T>` con un método `IsSatisfiedBy(T candidate)`. Cada regla concreta implementa esta interfaz. Por ejemplo, `IsActive` verifica si el cliente está activo, y `HasCredit` verifica si tiene crédito suficiente. La lógica es simple y aislada, lo que permite pruebas unitarias directas para cada regla.
El siguiente paso es permitir composición. Para ello, se crean especificaciones compuestas: `AndSpecification`, `OrSpecification` y `NotSpecification`. Estas reciben otras especificaciones y combinan sus resultados. Por ejemplo, `AndSpecification` retorna verdadero si ambas especificaciones son verdaderas. Esta composición permite construir reglas complejas sin reescribir lógica. Además, mantiene la expresividad: la regla final se lee como una sentencia de negocio.
En implementaciones más avanzadas, se puede incluir soporte para expresiones. En lugar de solo un `IsSatisfiedBy`, se puede exponer una expresión `Expression<Func<T,bool>>` que permite traducir la regla a consultas de base de datos. Esto es útil en repositorios que necesitan filtrar datos en el servidor, no en memoria. Así, la misma especificación puede usarse tanto para validar objetos en memoria como para generar consultas en SQL o LINQ. Este enfoque evita duplicar reglas y reduce inconsistencias.
La implementación también debe considerar el contexto. Algunas reglas requieren información externa, como fechas actuales o configuraciones. En esos casos, las especificaciones pueden recibir dependencias a través de inyección. Por ejemplo, una regla de “cliente con contrato vigente” puede depender de un servicio de fechas. Esto mantiene la regla testable porque se puede inyectar un reloj de prueba. El patrón no requiere que las especificaciones sean totalmente puras, pero sí que sus dependencias sean explícitas.
Otro aspecto importante es la nomenclatura. Las especificaciones deben tener nombres que reflejen reglas del dominio, no detalles técnicos. Por ejemplo, `IsEligibleForDiscount` es más claro que `DiscountRule1`. Esto mejora la comunicación entre equipos técnicos y de negocio. Cuando el código expresa reglas de dominio con nombres claros, se reduce la distancia entre requerimientos y implementación.
Finalmente, la implementación debe tener en cuenta el ciclo de vida. Las especificaciones pueden crecer en número y volverse difíciles de gestionar si no se organizan bien. Es recomendable agruparlas por contexto o por agregados del dominio. También se debe evitar crear especificaciones demasiado pequeñas que se vuelvan ruidosas. El equilibrio correcto es reglas claras y reutilizables, sin fragmentar innecesariamente. En resumen, la implementación en C# requiere una interfaz clara, composición, y una estrategia para mantener las reglas alineadas con el dominio.
4. Specification vs ifs dispersos
Los `if` dispersos son la forma más común de implementar reglas, pero también la más difícil de mantener a largo plazo. Cuando una regla de negocio se repite en distintos lugares, cualquier cambio requiere encontrar y modificar múltiples puntos. Esto introduce riesgo de inconsistencias: un lugar se actualiza, otro no. Specification centraliza las reglas, lo que reduce ese riesgo y facilita el mantenimiento.
Otra diferencia es la legibilidad. Un bloque de `if` con condiciones complejas es difícil de leer y de explicar. En cambio, una especificación con un nombre explícito hace que la regla sea autoexplicativa. Por ejemplo, `IsEligibleForDiscount` expresa una intención clara, mientras que un `if` con múltiples condiciones no lo hace. Esta claridad es valiosa para equipos grandes y para la comunicación con el negocio.
La composabilidad es otra ventaja. Con `if` dispersos, combinar reglas requiere duplicar lógica o crear condiciones difíciles de entender. Con Specification, la composición es un primer ciudadano: AND, OR y NOT son operaciones estándar. Esto permite construir reglas complejas de forma declarativa y modular. Además, las especificaciones pueden ser reutilizadas en diferentes contextos, mientras que los `if` suelen quedar atados al flujo donde se escribieron.
En términos de pruebas, las especificaciones son más testables. Cada regla se prueba de forma aislada con casos simples. En un bloque de `if`, la regla está mezclada con otras, lo que hace más difícil aislar comportamientos. Esto aumenta el esfuerzo de testing y reduce la confianza en la lógica. Specification mejora la calidad porque favorece pruebas unitarias claras.
Sin embargo, Specification introduce más clases y objetos, lo que puede parecer sobreingeniería si las reglas son pocas. En esos casos, un `if` simple puede ser suficiente. La diferencia clave es el tamaño y la evolución de las reglas. Cuando las reglas crecen, se combinan y cambian con frecuencia, Specification ofrece un mejor camino. Cuando las reglas son triviales, el costo de crear una especificación puede no valer la pena.
En resumen, los `if` dispersos son rápidos de escribir pero difíciles de mantener. Specification requiere más estructura, pero aporta claridad, composabilidad y testabilidad. La elección debe basarse en la complejidad actual y futura de las reglas. Para dominios con reglas ricas, Specification es una inversión que reduce deuda técnica y mejora la calidad.
5. Diagrama UML
El UML del patrón Specification suele mostrar una interfaz `ISpecification<T>` con un método `IsSatisfiedBy(T candidate)`. A partir de esa interfaz, se derivan especificaciones concretas como `IsActive` o `HasCredit`. Además, se incluyen especificaciones compuestas como `AndSpecification`, `OrSpecification` y `NotSpecification`, que contienen referencias a otras especificaciones. Esta estructura refleja la composabilidad del patrón.
En el diagrama, las especificaciones compuestas se representan como clases que implementan la interfaz y contienen otras especificaciones. Por ejemplo, `AndSpecification` tiene dos campos `left` y `right`. Su método `IsSatisfiedBy` evalúa ambas y retorna verdadero si ambas son verdaderas. Esta representación explica cómo se construyen reglas complejas sin duplicar lógica. El UML también puede mostrar cómo el cliente usa una especificación compuesta para evaluar un objeto de dominio.
El diagrama de flujo complementario muestra el proceso de evaluación: se recibe un candidato, se evalúa una especificación base, se evalúa otra, y luego se combina el resultado. Este flujo ayuda a entender que la evaluación es una operación lógica, no una transformación. También permite visualizar cómo se puede detener la evaluación tempranamente en combinaciones AND o OR, lo que puede ser relevante para optimización.
En implementaciones que soportan expresiones, el UML puede incluir un método adicional `ToExpression()` que retorna una expresión lógica. Esta capacidad se usa para construir consultas en repositorios. En el diagrama, esto se puede representar como una dependencia entre la especificación y el repositorio, mostrando que el repositorio usa la expresión para filtrar datos. Esta vista ayuda a entender por qué el patrón es útil tanto en memoria como en base de datos.
También se puede representar un `SpecificationBuilder` o una clase de extensión que facilita la composición con métodos `And`, `Or` y `Not`. Esto aporta fluidez a la construcción de reglas. En UML, se representa como una clase que produce especificaciones compuestas. Esto no es obligatorio, pero es común en implementaciones modernas para mejorar legibilidad.
En resumen, el UML del patrón Specification ilustra una jerarquía de reglas y su composición. Muestra cómo una regla simple puede convertirse en una regla compleja sin perder claridad. Visualizar esta estructura ayuda a diseñar especificaciones consistentes y a comunicar el modelo de reglas al equipo.

Prompt: UML specification pattern, clean vector.

Prompt: specification flow diagram, minimal infographic.
⚠️ Cuándo NO Usar Specification
Specification no siempre es necesario. Si las reglas son pocas, simples y estables, introducir un patrón con múltiples clases puede ser un exceso. En esos casos, un `if` claro y bien ubicado puede ser suficiente. El costo de crear especificaciones puede superar el beneficio cuando el dominio no tiene reglas complejas ni composición significativa.
Otro escenario es cuando las reglas cambian muy poco y se aplican en un solo lugar. En ese caso, encapsularlas en objetos reutilizables no aporta valor real. El patrón es útil cuando las reglas se usan en múltiples contextos o cuando se combinan dinámicamente. Si eso no ocurre, la complejidad adicional puede ser innecesaria.
También puede ser problemático cuando el equipo no está familiarizado con el patrón. Si no se comprende la intención, es común crear demasiadas especificaciones pequeñas o usar composiciones innecesariamente complejas. Esto puede hacer que el código sea más difícil de seguir que un `if` directo. En esos casos, la adopción del patrón sin una guía clara puede ser contraproducente.
En sistemas donde el rendimiento es crítico, la evaluación de múltiples especificaciones en memoria puede introducir overhead. Aunque suele ser pequeño, en flujos de alto volumen puede ser relevante. Si las reglas pueden expresarse de forma más eficiente en una consulta directa, y no se necesita reutilización, quizá sea mejor evitar el patrón. En cualquier caso, la decisión debe basarse en requisitos reales y no en preferencia personal.
Finalmente, si las reglas no tienen una estructura clara o cambian de forma impredecible, el patrón puede volverse difícil de mantener. Specification funciona mejor cuando las reglas pueden expresarse en términos lógicos y composables. Si las reglas son altamente contextuales y no se pueden reducir a predicados claros, el patrón puede resultar forzado. En resumen, Specification es valioso cuando la complejidad y la reutilización lo justifican; de lo contrario, un enfoque más simple puede ser más adecuado.
💪 Ejercicio
Diseña un conjunto de especificaciones para filtrar órdenes pendientes en un sistema de e-commerce. Define reglas como: orden en estado “Pendiente”, orden con pago confirmado, orden con stock disponible y orden con dirección válida. Cada regla debe implementarse como una especificación independiente. El objetivo es poder combinar estas reglas según distintos escenarios de negocio.
En la segunda fase, construye una especificación compuesta que represente “Orden lista para envío”. Esta regla podría ser: Pendiente AND PagoConfirmado AND StockDisponible AND DirecciónVálida. Explica cómo combinarías las especificaciones y cómo evaluarías la regla sobre una orden. Este paso demuestra la composición con AND de múltiples reglas simples.
En la tercera fase, define una regla alternativa: “Orden pendiente pero con excepción”. Por ejemplo, una orden podría ser elegible aunque falte stock si el cliente es premium. Esto requiere usar OR y quizás NOT. Diseña una especificación compuesta con esas condiciones y explica cómo se evalúa. El objetivo es practicar composición lógica más compleja.
En la cuarta fase, define cómo reutilizarías estas especificaciones en un repositorio para filtrar datos en la base de datos. Describe si usarías expresiones para traducir las reglas a LINQ y evitar cargar todo en memoria. Esto ayuda a conectar el patrón con la persistencia y evita inconsistencias entre reglas de negocio y consultas.
Finalmente, crea un conjunto de pruebas unitarias para cada especificación simple y para las compuestas. Define casos positivos y negativos. El ejercicio se completa cuando tienes reglas claras, composiciones significativas y pruebas que validan el comportamiento. Esto demuestra la utilidad del patrón para estructurar reglas de negocio de forma mantenible.
Conclusión
Specification es un patrón orientado a la claridad y la coherencia de reglas de negocio. Encapsular reglas en objetos permite que la lógica sea reutilizable, testeable y fácilmente combinable. En dominios donde las decisiones dependen de múltiples condiciones, este enfoque evita la dispersión de `if` y reduce el riesgo de inconsistencias.
La capacidad de composición es su mayor ventaja. Las reglas simples se pueden combinar con AND, OR y NOT para expresar decisiones complejas de forma declarativa. Esto mejora la legibilidad y facilita el diálogo con el negocio, porque las reglas tienen nombres claros y se pueden describir con lenguaje del dominio.
En C#, la implementación es directa y se integra bien con LINQ y con repositorios. Cuando se combina con expresiones, el patrón puede usarse tanto en memoria como en consultas a base de datos, evitando duplicar lógica. Esto contribuye a la consistencia del sistema en todas las capas.
Sin embargo, el patrón no es una solución universal. Tiene un costo en clases adicionales y complejidad estructural. Debe usarse cuando las reglas son numerosas, se combinan o cambian con frecuencia. Para reglas simples y estables, un enfoque directo puede ser suficiente. La clave está en aplicar el patrón donde aporta valor real.
En conclusión, Specification es una herramienta poderosa para modelar reglas de negocio de forma clara y mantenible. Cuando se aplica con criterio, reduce deuda técnica, mejora la testabilidad y ayuda a expresar decisiones complejas de manera consistente. Es un patrón esencial en dominios ricos y en arquitecturas orientadas a reglas.