Saltar al pie de página
Iron Academy Logo
Aprender C#
Aprender C#

Otras categorías

Doble Despacho C#: Cuando inyectar dependencias tiene sentido - Explicado mediante el ejemplo de diseño basado en dominios de Derek Comartin

Derek Comartin
8m 44s

En el campo del lenguaje de programación C#, el doble envío es a menudo malinterpretado o infrautilizado. Es una técnica poderosa que permite el comportamiento polimórfico entre dos objetos involucrados, especialmente cuando se maneja el comportamiento a través de clases derivadas.

En su vídeo "Double Dispatch in DDD: When Injecting Dependencies Makes Sense", Derek Comartin desglosa cómo el doble envío encaja perfectamente en el Diseño Orientado al Dominio (DDD). Exploraremos sus ejemplos en profundidad, mostrando cómo puede reducir la carga de mantenimiento, simplificar el código existente e incluso imitar patrones como el de visitante, comúnmente visto en otros lenguajes.

Si alguna vez se ha enfrentado a las limitaciones del envío único en C#, o ha intentado determinar el comportamiento basándose tanto en la instancia de un objeto como en una estrategia o regla inyectada, este artículo le ayudará a aclarar cómo puede utilizarse eficazmente el envío doble en C#.

Por qué inyectar comportamiento tiene sentido en los modelos de dominio

Derek comienza señalando un dogma común en DDD - que su modelo de dominio debe tener cero dependencias. Pero eso no siempre es práctico o útil. Al modelar el comportamiento, puede ser necesario inyectar lógica, como reglas, políticas o estrategias. Aquí es donde ayuda el doble envío: puedes pasar un concepto de dominio, como una política, al objeto de dominio y dejar que la evaluación basada en métodos se gestione externamente, aunque el objeto de dominio sigue teniendo el control final.

Este patrón tiene sentido si se tiene en cuenta a qué se está acoplando: al comportamiento del dominio, no a la infraestructura.

El mal ejemplo: Lógica codificada en el dominio

Para demostrar el problema, Derek comienza con una clase de envío que contiene lógica codificada para determinar el retraso:

public bool IsLate(DateTime expectedDelivery)
{
    return _systemClock.Now() > expectedDelivery;
}
public bool IsLate(DateTime expectedDelivery)
{
    return _systemClock.Now() > expectedDelivery;
}

El código existente es sencillo pero inflexible. Se basa en decisiones en tiempo de compilación y vincula estrechamente el comportamiento a la clase. Cambiar la regla requeriría cambiar el modelo de dominio, y las pruebas son más difíciles porque dependen del tiempo.

El Refactor: Uso de políticas y doble despacho

Derek presenta la interfaz IDeliveryTimingPolicy:

public interface IDeliveryTimingPolicy
{
    bool IsLate(Shipment shipment);
}
public interface IDeliveryTimingPolicy
{
    bool IsLate(Shipment shipment);
}

A continuación, crea dos implementaciones:

  1. Política de plazos de entrega estándar

  2. Política de tiempo de entrega en búfer

Estas clases reciben el envío y devuelven un booleano basado en su regla. A continuación se muestra un fragmento de código de BufferedDeliveryTimingPolicy:

public bool IsLate(Shipment shipment)
{
    return _clock.Now() > shipment.DeliveryDate.AddMinutes(30);
}
public bool IsLate(Shipment shipment)
{
    return _clock.Now() > shipment.DeliveryDate.AddMinutes(30);
}

Ahora, en la clase Shipment, utilizamos double dispatch:

public bool IsLate(IDeliveryTimingPolicy policy)
{
    return policy.IsLate(this);
}
public bool IsLate(IDeliveryTimingPolicy policy)
{
    return policy.IsLate(this);
}

Se trata de un envío doble clásico: el primer envío es llamar a IsLate() en el envío y pasar la política; el segundo envío es llamar a IsLate() en la política con el envío como parámetro. Hay dos objetos implicados, cada uno de los cuales participa en la determinación del comportamiento, algo que no puede conseguirse con un único envío.

Beneficios en las pruebas y flexibilidad

Este enfoque da como resultado un código altamente comprobable y determinista. Derek muestra ejemplos que utilizan políticas estándar y con búfer, donde los datos de prueba se controlan y el tipo de tiempo de ejecución de cada objeto determina el comportamiento.

var policy = new BufferedDeliveryTimingPolicy(TimeSpan.FromMinutes(30));
var shipment = new Shipment { DeliveryDate = DateTime.UtcNow.AddMinutes(-15) };
Assert.False(shipment.IsLate(policy)); // Because of 30-minute buffer
var policy = new BufferedDeliveryTimingPolicy(TimeSpan.FromMinutes(30));
var shipment = new Shipment { DeliveryDate = DateTime.UtcNow.AddMinutes(-15) };
Assert.False(shipment.IsLate(policy)); // Because of 30-minute buffer

Esto demuestra cómo se puede alterar el comportamiento en tiempo de ejecución sin cambiar el modelo de dominio. Se obtiene un comportamiento polimórfico basado en la naturaleza dinámica de la política, al tiempo que se mantiene la integridad del dominio.

Double Dispatch vs Visitor Pattern en C

El ejemplo de Derek se asemeja al patrón de visitante, en el que se define un conjunto de operaciones para realizar en objetos sin cambiar su estructura. Normalmente, en el patrón del visitante, se vería algo como:

public abstract class Shape
{
    public abstract void Accept(IVisitor visitor);
}

public class Circle : Shape
{
    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}
public abstract class Shape
{
    public abstract void Accept(IVisitor visitor);
}

public class Circle : Shape
{
    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

Aquí, los métodos Accept() y Visit() se coordinan a través de dos tipos-el objeto y el visitante-al igual que el uso que hace Derek de Shipment e IDeliveryTimingPolicy. Este patrón ayuda a implementar comportamientos de envío múltiple incluso en un lenguaje de envío único como C#.

Composición de reglas con colecciones de políticas

En otro ejemplo siguiente, Derek muestra cómo se pueden evaluar varias reglas mediante una colección. Introduce reglas como:

  • Tiene una regla de destino válida

  • TodosLosEmpaquetadosRegla

  • Regla no enviada ya

Cada uno implementa IShipmentReadinessRule con:

public bool IsSatisfiedBy(Shipment shipment)
public bool IsSatisfiedBy(Shipment shipment)

A continuación, la clase Envío las evalúa de la siguiente manera:

public bool CanShip(IEnumerable<IShipmentReadinessRule> rules)
{
    return rules.All(rule => rule.IsSatisfiedBy(this));
}
public bool CanShip(IEnumerable<IShipmentReadinessRule> rules)
{
    return rules.All(rule => rule.IsSatisfiedBy(this));
}

Este patrón void accept permite evaluar múltiples reglas de dominio de forma dinámica. Si almacena reglas en la configuración (especialmente en una aplicación multi-tenant), puede crear una nueva lista en tiempo de ejecución y pasarla al envío.

Reglas dinámicas en aplicaciones multiusuario

Derek señala que en las aplicaciones multiusuario, el conjunto de reglas puede cambiar en función del cliente. Se podría obtener una lista de políticas del almacenamiento e inyectarlas dinámicamente:

var rules = LoadRulesForCustomer(customerId);
var canShip = shipment.CanShip(rules);
var rules = LoadRulesForCustomer(customerId);
var canShip = shipment.CanShip(rules);

Esto muestra cómo el despacho dinámico y la toma de decisiones en tiempo de ejecución pueden integrarse en su aplicación. El comportamiento deseado se consigue cambiando la configuración, no el modelo.

Aclarando conceptos erróneos: No todas las dependencias son malas

Hacia el final, Derek aborda la idea errónea de que inyectar cualquier cosa en un dominio es malo. Subraya que lo que importa es lo que estás inyectando. No es lo mismo inyectar un comportamiento de dominio, como especificaciones o políticas, que una infraestructura.

El modelo de dominio sigue siendo el punto de entrada. Es el propietario de la decisión, pero puede delegarla en otros objetos, siempre que estén dentro del mismo contexto de dominio.

Resumiendo: Por qué Double Dispatch C# es potente en DDD

Derek termina instándonos a pensar de forma crítica: no tengas miedo de void visit, public virtual void accept, o patrones similares cuando aportan claridad y mantenibilidad. Al inyectar lógica empresarial de forma controlada, se gana en flexibilidad y precisión.

Así pues, tanto si está trabajando en una nueva clase como en la refactorización de una base de código existente, el doble envío en C# le ofrece una forma limpia de separar las preocupaciones al tiempo que mantiene el enfoque en el dominio.

Si utilizas políticas, especificaciones o incluso construyes un patrón de visitante sin darte cuenta, ya estás cerca de implementar el doble envío. Su comprensión proporciona un mayor control sobre el comportamiento del código en tiempo de ejecución, mejorando la capacidad de prueba y la adaptabilidad.

Conclusión

En resumen: el doble envío en C# puede ser una solución elegante y práctica para inyectar lógica de dominio, preservar la encapsulación y admitir un comportamiento flexible. Cuando se utiliza con patrones como visitante, envío múltiple y uso dinámico de palabras clave (cuidadosamente), permite escribir modelos de dominio expresivos y robustos.

Así que la próxima vez que llame a shipment.IsLate(policy)sepa que está aprovechando un patrón fundamental que acerca a C# a un diseño verdaderamente polimórfico.

Sugerencia de ejemplo: Si está creando una clase PurchaseOrder y desea determinar si se puede añadir un artículo en función de una política, intente pasar la política a PurchaseOrder y deje que la política visite el pedido. Así es el doble envío en acción.

Vea el vídeo completo en su canal de YouTube Channel y obtenga más información sobre el tema.

Hero Worlddot related to Doble Despacho C#: Cuando inyectar dependencias tiene sentido - Explicado mediante el ejemplo d...
Hero Affiliate related to Doble Despacho C#: Cuando inyectar dependencias tiene sentido - Explicado mediante el ejemplo ...

Gana más compartiendo lo que te gusta

¿Creas contenidos para desarrolladores que trabajan con .NET, C#, Java, Python o Node.js? ¡Convierte tu experiencia en un ingreso extra!

Equipo de soporte de Iron

Estamos disponibles online las 24 horas, 5 días a la semana.
Chat
Email
Llámame