Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

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#.
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.
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 Function IsLate(expectedDelivery As DateTime) As Boolean
Return _systemClock.Now() > expectedDelivery
End FunctionEl 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.
Derek presenta la interfaz IDeliveryTimingPolicy:
public interface IDeliveryTimingPolicy
{
bool IsLate(Shipment shipment);
}Public Interface IDeliveryTimingPolicy
Function IsLate(shipment As Shipment) As Boolean
End InterfaceA continuación, crea dos implementaciones:
Política de plazos de entrega estándar
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 Function IsLate(shipment As Shipment) As Boolean
Return _clock.Now() > shipment.DeliveryDate.AddMinutes(30)
End FunctionAhora, en la clase Shipment, utilizamos double dispatch:
public bool IsLate(IDeliveryTimingPolicy policy)
{
return policy.IsLate(this);
}Public Function IsLate(ByVal policy As IDeliveryTimingPolicy) As Boolean
Return policy.IsLate(Me)
End FunctionSe 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.
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 bufferDim policy = New BufferedDeliveryTimingPolicy(TimeSpan.FromMinutes(30))
Dim shipment = New Shipment With {.DeliveryDate = DateTime.UtcNow.AddMinutes(-15)}
Assert.False(shipment.IsLate(policy)) ' Because of 30-minute bufferEsto 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.
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 MustInherit Class Shape
Public MustOverride Sub Accept(visitor As IVisitor)
End Class
Public Class Circle
Inherits Shape
Public Overrides Sub Accept(visitor As IVisitor)
visitor.Visit(Me)
End Sub
End ClassAquí, 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#.
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 Function IsSatisfiedBy(shipment As Shipment) As BooleanA 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 Function CanShip(rules As IEnumerable(Of IShipmentReadinessRule)) As Boolean
Return rules.All(Function(rule) rule.IsSatisfiedBy(Me))
End FunctionEste 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.
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);Dim rules = LoadRulesForCustomer(customerId)
Dim 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 logra cambiando la configuración, no el modelo.
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.
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.
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.
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy