Ir para o conteúdo do rodapé
Iron Academy Logo
Aprenda C#
Aprenda C#

Outras categorias

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

Derek Comartin
8m 44s

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:

  1. Política de tempo de entrega padrão

  2. 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.

Hero Worlddot related to Despacho Duplo em C#: Quando a Injeção de Dependências Faz Sentido – Explicado através do...
Hero Affiliate related to Despacho Duplo em C#: Quando a Injeção de Dependências Faz Sentido – Explicado através d...

Ganhe mais compartilhando o que você ama.

Você cria conteúdo para desenvolvedores que trabalham com .NET, C#, Java, Python ou Node.js? Transforme sua expertise em renda extra!

Equipe de suporte de ferro

Estamos online 24 horas por dia, 5 dias por semana.
Bater papo
E-mail
Liga para mim