Despacho Duplo em C#: Quando a Injeção de Dependências Faz Sentido – Explicado através do Exemplo de Domain-Driven Design de Derek Comartin
No campo da linguagem de programação C#, o despacho duplo é frequentemente mal compreendido ou subutilizado. É uma técnica poderosa que permite o comportamento polimórfico entre dois objetos envolvidos, especialmente ao lidar com o comportamento entre classes derivadas.
Em seu vídeo " Double Dispatch in DDD: When Injecting Dependencies Makes Sense ", Derek Comartin explica como o despacho duplo se encaixa perfeitamente no Domain-Driven Design (DDD). Analisaremos seus exemplos em detalhes, mostrando como eles podem reduzir a carga de manutenção, simplificar o código existente e até mesmo imitar padrões como o padrão Visitor, comumente visto em outras linguagens.
Se você já lidou com as limitações do despacho simples em C#, ou tentou determinar o comportamento com base tanto na instância de um objeto quanto em uma estratégia ou regra injetada, este artigo ajudará a esclarecer como o despacho duplo em C# pode ser usado de forma eficaz.
Por que injetar comportamento faz sentido em modelos de domínio
Derek começa por destacar um dogma comum no DDD: o de que seu modelo de domínio não deve ter nenhuma dependência. Mas isso nem sempre é prático ou útil. Ao modelar o comportamento, pode ser necessário inserir lógica, como regras, políticas ou estratégias. É aqui que o despacho duplo ajuda: você pode passar um conceito de domínio, como uma política, para o objeto de domínio e deixar que a avaliação baseada em método seja tratada externamente — mantendo, ainda assim, o controle final sob o objeto de domínio.
Esse padrão faz sentido quando você considera a que está se acoplando: ao comportamento do domínio, e não à infraestrutura.
O mau exemplo: lógica codificada diretamente no domínio.
Para demonstrar o problema, Derek começa com uma classe Shipment que contém lógica codificada para determinar o atraso:
public bool IsLate(DateTime expectedDelivery)
{
return _systemClock.Now() > expectedDelivery;
}
public bool IsLate(DateTime expectedDelivery)
{
return _systemClock.Now() > expectedDelivery;
}
O código existente é simples, mas inflexível. Depende de decisões tomadas em tempo de compilação e acopla fortemente o comportamento à classe. Alterar a regra exigiria alterar o modelo de domínio, e os testes são mais difíceis, pois dependem do tempo.
A Refatoração: Utilizando Políticas e Despacho Duplo
Derek apresenta a interface IDeliveryTimingPolicy:
public interface IDeliveryTimingPolicy
{
bool IsLate(Shipment shipment);
}
public interface IDeliveryTimingPolicy
{
bool IsLate(Shipment shipment);
}
Ele então cria duas implementações:
-
Política de tempo de entrega padrão
- Política de tempo de entrega em buffer
Essas classes recebem o Shipment e retornam um valor booleano com base em sua regra. Segue abaixo um trecho de código da 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);
}
Agora, na classe Shipment, usamos despacho duplo :
public bool IsLate(IDeliveryTimingPolicy policy)
{
return policy.IsLate(this);
}
public bool IsLate(IDeliveryTimingPolicy policy)
{
return policy.IsLate(this);
}
Este é um exemplo clássico de despacho duplo: o primeiro despacho chama IsLate() na remessa e passa a política; O segundo despacho está chamando IsLate() na política com a remessa como parâmetro. Dois objetos estão envolvidos, cada um participando na determinação do comportamento — algo que o despacho único não consegue alcançar.
Benefícios em Testes e Flexibilidade
Essa abordagem resulta em código altamente testável e determinístico. Derek mostra exemplos usando políticas padrão e em buffer, onde os dados de teste são controlados e o tipo de tempo de execução de cada objeto determina o comportamento.
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
Isso demonstra como o comportamento em tempo de execução pode ser alterado sem modificar o modelo de domínio. Você obtém um comportamento polimórfico baseado na natureza dinâmica da política, mantendo a integridade do domínio.
Padrão Double Dispatch vs Visitor em C
O exemplo de Derek se assemelha ao padrão Visitor, onde você define um conjunto de operações a serem executadas em objetos sem alterar sua estrutura. Normalmente, no padrão de visitante, você veria 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);
}
}
Aqui, os métodos Accept() e Visit() são coordenados entre dois tipos — o objeto e o visitante — assim como Derek faz com Shipment e IDeliveryTimingPolicy. Esse padrão ajuda a implementar o comportamento de despacho múltiplo mesmo em uma linguagem de despacho único como C#.
Compondo regras com conjuntos de políticas
Em outro exemplo a seguir, Derek mostra como várias regras podem ser avaliadas por meio de uma coleção. Ele introduz regras como:
-
RegraHasValidDestination
-
Regra de Pacotes Empacotados
- Regra de não enviado
Cada um implementa IShipmentReadinessRule com:
public bool IsSatisfiedBy(Shipment shipment)
public bool IsSatisfiedBy(Shipment shipment)
Em seguida, a classe Shipment os avalia da seguinte forma:
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 padrão de aceitação vazio permite avaliar várias regras de domínio dinamicamente. Se você armazenar regras na configuração (especialmente em um aplicativo multi-tenant), poderá criar uma nova lista em tempo de execução e passá-la para o envio.
Regras dinâmicas em aplicações multi-inquilino
Derek destaca que, em aplicações multi-inquilino, o conjunto de regras pode variar de cliente para cliente. Você pode buscar uma lista de políticas no armazenamento e injetá-las dinamicamente:
var rules = LoadRulesForCustomer(customerId);
var canShip = shipment.CanShip(rules);
var rules = LoadRulesForCustomer(customerId);
var canShip = shipment.CanShip(rules);
Isso demonstra como o despacho dinâmico e a tomada de decisões em tempo de execução podem ser integrados à sua aplicação. O comportamento desejado é alcançado alterando a configuração, não o modelo.
Esclarecendo equívocos: nem todas as dependências são ruins.
Na parte final, Derek aborda a ideia errada de que injetar qualquer coisa em um domínio é ruim. Ele enfatiza que o que importa é o que você está injetando. Injetar comportamento de domínio, como especificações ou políticas, não é o mesmo que injetar infraestrutura.
O modelo de domínio continua sendo o ponto de partida. A decisão é dela, mas pode ser delegada a outros objetos, desde que estejam dentro do mesmo contexto de domínio.
Concluindo: Por que o Double Dispatch em C# é poderoso no DDD
Derek conclui nos incentivando a pensar criticamente: não tenhamos medo de visitas ao vazio, aceitação pública do vazio virtual ou padrões semelhantes quando eles trazem clareza e facilidade de manutenção. Ao injetar lógica de negócios de forma controlada, você ganha flexibilidade e precisão.
Portanto, seja você trabalhando em uma nova classe ou refatorando uma base de código existente, o despacho duplo em C# oferece uma maneira clara de separar responsabilidades, mantendo o foco no domínio.
Se você estiver usando políticas, especificações ou até mesmo construindo um padrão de visitante sem perceber, já está perto de implementar o despacho duplo. Compreender isso lhe dá mais controle sobre como o código se comporta em tempo de execução, melhorando a testabilidade e a adaptabilidade.
Conclusão
Resumindo: o despacho duplo em C# pode ser uma solução elegante e prática para injetar lógica de domínio, preservar o encapsulamento e suportar comportamento flexível. Quando usado com padrões como visitante, despacho múltiplo e uso dinâmico de palavras-chave (com cuidado), permite escrever modelos de domínio expressivos e robustos.
Então, da próxima vez que você chamar shipping.IsLate(policy)—saiba que você está aproveitando um padrão fundamental que aproxima o C# de um design verdadeiramente polimórfico.
Exemplo de dica: Se você estiver criando uma classe PurchaseOrder e quiser determinar se um Item pode ser adicionado com base em uma política, tente passar a política para o PurchaseOrder e deixe a política visitar o pedido. Isso é despacho duplo em ação.
Assista ao vídeo completo no canal dele no YouTube e obtenha mais informações sobre o assunto.
