C# - O padrão Humble Object


  Hoje vou apresentar o padrão Humble Object. (Humble Object Pattern)

O Humble Object Pattern é um padrão de design de software que visa facilitar a testabilidade de componentes complexos. Ele ajuda a isolar a lógica de negócios de um componente dos detalhes de implementação que podem ser difíceis de testar, como dependências externas, serviços de infraestrutura ou APIs externas.

O padrão Humble Object envolve a criação de uma interface humilde (humble interface) que define a funcionalidade principal do componente. Essa interface define os métodos e propriedades que representam as principais operações do componente.

Em seguida, o componente é dividido em duas partes:

  1. Humble Object (Objeto Humilde):

    Essa parte do componente implementa a interface humilde e contém a lógica de negócios principal do componente. Ela é projetada para ser simples, independente de contexto e facilmente testável. Todas as dependências externas, como acesso a banco de dados, serviços web ou interações com o sistema operacional, são isoladas e injetadas no objeto humilde por meio de interfaces.
     
  2. Object Support (Suporte do Objeto):

    Essa parte do componente lida com as dependências externas e detalhes de implementação. Ela contém a lógica de infraestrutura, como acesso a banco de dados, chamadas de serviço web ou interações com o sistema operacional. O objetivo é manter essa parte o mais simples e direta possível, limitando-a apenas às tarefas necessárias para lidar com as dependências externas.

A ideia por trás do Humble Object Pattern é que a parte humilde do componente é mais fácil de testar, pois não possui dependências externas complexas. Os testes de unidade podem ser escritos facilmente para a parte humilde, sem a necessidade de lidar com configurações complexas ou interações com recursos externos.

Assim, quando criamos programas, eles geralmente precisam se comunicar com outras partes do computador ou com o mundo externo, como um banco de dados ou serviço de Internet. Mas quando queremos testar nossos programas para garantir que funcionem corretamente, pode ser difícil conversar com essas outras partes.

Para resolver esse problema, podemos usar o Humble Object Pattern para separar as partes do nosso programa que precisam se comunicar com outras partes do computador ou com o mundo exterior das partes do programa que não precisam. As partes do programa que não precisam falar com o mundo exterior são muito mais fáceis de testar porque podemos testá-las isoladamente sem nos preocuparmos com outras coisas.

No C#, você pode implementar o Humble Object Pattern utilizando interfaces para definir a interface humilde e separar a lógica de negócios dos detalhes de implementação. A injeção de dependência é frequentemente usada para fornecer as dependências externas para o objeto humilde.

A seguir vamos dar um exemplo para ilustrar o uso deste padrão.

Usando o padrão Humble Object

Imagine que você tenha um componente chamado OrderProcessor responsável por processar os pedidos dos clientes. Esse componente precisa interagir com um serviço externo de pagamento para autorizar os pagamentos dos pedidos.

Vamos criar um projeto console no VS 2022 usando o .NET 7.0 e criar 3 classes para definir o modelo de domínio (vamos usar uma abordagem simplificada):

1- Order

public class Order
{
 
public int OrderId { get; set; }
 
public string CustomerName { get; set; }
 
public List<Product> Products { get; set; }
 
public Payment Payment { get; set; }

  public
Order(int orderId, string customerName, List<Product> products, Payment payment)
  {
    OrderId = orderId;
    CustomerName = customerName;
    Products = products;
    Payment = payment;
  }
}

2- Product

public class Product
{
 
public int ProductId { get; set; }
 
public string Name { get; set; }
 
public decimal Price { get; set; }

 
public Product(int productId, string name, decimal price)
  {
    ProductId = productId;
    Name = name;
    Price = price;
  }
}

3- Payment

public class Payment
{
 
public string PaymentMethod { get; set; }
 
public decimal Amount { get; set; }

  public Payment(string paymentMethod, decimal amount)
  {
    PaymentMethod = paymentMethod;
    Amount = amount;
  }
}

Definindo o serviço externo de pagamento usando a interface IPaymentService:

public interface IPaymentService
{
 
void AuthorizePayment(Payment payment);
 
void CapturePayment(Payment payment);
}

A implementação seria feita na classe PaymentService que não irei mostrar para simplificar o código.

Vamos aplicar o Humble Object Pattern nesse caso.

1- Definindo a interface humilde (Humble)

public interface IOrderProcessor
{
  
void ProcessOrder(Order order);
}

2- Implementando o objeto humilde

public class OrderProcessor : IOrderProcessor
{
 
private readonly IPaymentService _paymentService;
 
public OrderProcessor(IPaymentService paymentService)
  {
    _paymentService = paymentService;
  }

 
public void ProcessOrder(Order order)
  {
   
// Lógica de negócio para processar o pedido

    // Chama o serviço de pagamento para autorizar o pagamento

    _paymentService.AuthorizePayment(order.Payment);

   
// Mais lógica de processamento do pedido
  }
}

No exemplo acima, o OrderProcessor é o objeto humilde que implementa a interface IOrderProcessor. Ele possui uma dependência no IPaymentService, que representa o serviço externo de pagamento.

Implementando o suporte do objeto:

public class PaymentService : IPaymentService
{
 
public void AuthorizePayment(Payment payment)
  {
   
throw new NotImplementedException();
  }
 
public void CapturePayment(Payment payment)
  {
   
throw new NotImplementedException();
  }
}

O PaymentService representa a implementação do serviço de pagamento externo. Ele lida com as interações reais com o serviço de pagamento.

Usando o Humble Object Pattern:


// Na configuração da injeção de dependência (exemplo usando ASP.NET Core):

services.AddScoped<IOrderProcessor, OrderProcessor>();
services.AddScoped<IPaymentService, PaymentService>();

No código acima, você registra as implementações das interfaces IOrderProcessor e IPaymentService na configuração da injeção de dependência do ASP.NET Core. Dessa forma, sempre que um componente precisar do IOrderProcessor ou do IPaymentService, o framework fornecerá as instâncias corretas.

Ao utilizar o Humble Object Pattern nesse exemplo, você pode testar o OrderProcessor facilmente, isolando-o das interações com o serviço de pagamento externo. Nos testes de unidade, você pode fornecer uma implementação simulada ou um mock do IPaymentService para verificar o comportamento do OrderProcessor sem depender de recursos externos ou interações complexas.

Essa abordagem torna os testes de unidade mais simples, mais rápidos e mais confiáveis, pois se concentram apenas na lógica de negócio do OrderProcessor, sem se preocupar com o serviço de pagamento real.

Ao mesmo tempo, a implementação real do PaymentService pode ser testada em níveis de teste mais abrangentes, como testes de integração ou testes de sistema, garantindo que as interações com o serviço de pagamento funcionem corretamente.

Essa separação entre o objeto humilde e o suporte do objeto ajuda a manter um código mais organizado, modular e de fácil manutenção. Além disso, o Humble Object Pattern facilita a implementação de testes automatizados e aumenta a testabilidade do seu código.

E estamos conversados.

"Jesus dizia, pois, aos judeus que criam nele: Se vós permanecerdes na minha palavra, verdadeiramente sereis meus discípulos; E conhecereis a verdade, e a verdade vos libertará."
João 8:31-32

Referências:


José Carlos Macoratti