Design Pattern — Mediator

Leo Prange
Qualyteam Engineering
5 min readFeb 26, 2019
Foto: Émile Perron

Conheci este padrão durante uma grande refatoração no sistema que estou ajudando a construir. Identificamos uma necessidade de melhorar nossos casos de uso e desacoplar componentes, ele está se demonstrando uma excelente solução devido a sua facilidade de uso, manutenção e escalabilidade.

O Problema

Em orientação a objetos utilizamos de classes para abstrair um conjunto de objetos com características semelhantes (FARINELLI, 2007).

Embora as lógicas são geralmente divididas entre as classes, normalmente vamos necessitar fazer ligações entre elas e consequentemente uma classe começa a conhecer as regras da outra.

Conforme esta situação vai ganhando proporção o problema de comunicação se torna cada vez mais complexo devido ao grande grafo de ligações no qual o programa se torna e com isso vem os problemas para realizar modificações (COOPER, 2002).

Gamma et al. (2007) afirma que o pior caso é onde cada objeto possui conhecimento de todos os outros objetos.

Esse acoplamento torna um objeto refém de outros, fazendo com que ele não consiga funcionar sozinho e assim surge um domínio monolítico.

O resultado de uma modelagem com comportamentos distribuídos e dependente de outros objetos, encaminha o desenvolvedor a criar formas mais complexas para realizar customização.

E como diria Elemar Jr, “Complexidade é Custo”.

Sobre o Mediator

Mediator é um padrão de projetos comportamental que segundo Gamma et al. (2007, p.257) tem a intenção de:

“Definir um objeto que encapsula a forma como um conjunto de objetos interage. O Mediator promove o acoplamento fraco ao evitar que os objetos se refiram uns aos outros explicitamente e permite variar suas interações independentemente.”

Gamma et al. (2007) sugere o uso do padrão mediator quando:

  • objetos se comunicam de forma especifica e complexa
  • reutilização de um objeto se torna difícil, devido a quantidade de objetos no qual ele se comunica
  • quando temos comportamentos distribuídos e esse comportamento deveria ser customizável.

Implementação

Vamos ao código! Utilizaremos um domínio simples para que possamos focar no pattern. Inclusive, não precisamos reinventar a roda: Já existem bibliotecoas que abstraem a complexidade técnica deste padrão. Neste exemplo iremos usar o MediatR, biblioteca amplamente utilizada pela comunidade .NET.

Os detalhes de implementação podem ser conferidos no repositório no GitHub. No README.md vai ter o tutorial de como iniciar o projeto. Instalar as bibliotecas e etc :).

Implementação — Domínio

Para testarmos o padrão de projeto vamos simular a substituição de um jogador em um jogo de futebol. O fluxo desta funcionalidade será o Técnico que solicita a substituição.

A Classe técnico possui ID, Nome e um Time que é uma lista de Jogadores. Seu único método público é o de pedir substituição, no nosso caso ele da um “Grito” pedindo a substituição.

Seguindo nosso fluxo, o Quarto árbitro vai levantar a placa informando quais jogadores serão substituídos:

E finalizando com a classe jogador que faz as ações de sair e entrar em campo.

Agora que temos nosso domínio modelado, precisamos de uma camada que exponha os métodos necesśarios para nossa aplicação ser utilizada.

Para orquestrar este caso de uso de forma desacoplada, iremos utilizar o padrão mediator.

Implementação — Command

O Comando é a especificação dos parâmetros necessários para realizar a operação, o comando é o que define sua ação, ele pode ter 1 ou mais resolvedores (Handlers).

Para implementar o mediator com o MediatR, o comando precisa implementar a interface IRequest, que pode ou não receber um generics. Este generics é o tipo do retorno do seu comando.

No exemplo, o comando vai implementar IRequest definindo seu retorno como um IEnumerable de Jogadores que será a escalação do time atualizada.

Implementação — CommandHandler

CommandHandler são os resolvedores, a classe assina um contrato onde é informado qual comando é resolvido e qual é o retorno da solução. Ao assinar o contrato, a classe deve implementar o método Handle e nele deve ser implementada a solução do comando.

Nosso handler vai conectar nossos objetos realizando a substituição, podemos criar métodos auxiliares para manter o método Handle mais limpo. Em aplicações reais de mercado normalmente teremos dependências como classes de validação e repositórios. Isso é perfeitamente possível com o MediatR, já que é possível realizar injeção de dependência.

Nossa aplicação é bem mais simples, e vamos apenas fazer com que o técnico solicite a substituição, o quarto árbitro levante a placa e os jogadores se movam para dentro e fora de campo.

Como resultado temos nossa implementação na camada de aplicação chamando isolando o domínio das lógicas, preocupações e necessidades das outras classes.

Isso abre espaço para uma arquitetura evolutiva. Caso um destes comportamentos fosse reutilizado por outras classes ele poderia se tornar um comando isolado, assim ele seria acessado pela comunicação com mediator.

Fica para reflexão as formas de resolver esta situações, mas o foco sempre deve ser em “proteger” o domínio para ele não ser afetado por terceiros.

Utilizando

Para executar seu comando é necessário injetar o IMediator em sua classe, e executar o método Send passando como parâmetro o comando.

Testes

Para realizar os testes dos resolvedores podemos instanciar o CommandHandler e chamar o método Handle.

Outras técnicas para realizar os testes podem ser encontrados no repositório do próprio MediatR: (https://github.com/jbogard/MediatR/tree/master/test/MediatR.Tests)

Conclusão

O mediator se mostra uma ótima opção para manter nosso domínio seguro e flexível a mudanças. A remoção da necessidade de uma classe precisar do comportamento de terceiros nos trás benefícios para a melhor construção de nossa solução, evitando o acoplamento desnecessário.

Os testes refletem diretamente uma funcionalidade de nosso sistema, seus testes retratam diretamente o comportamento de nossa aplicação facilitando o uso de técnicas como BDD.

Esse padrão vem ganhando espaço com a necessidade de softwares mais robustos demandarem de técnicas como DDD, CQRS e microsserviços.

Espero poder ter ajudado a tornar o mediator mais uma opção para sua coleção do soluções.

Ficou com dúvida? deixe nos comentários :)

Gostou? deixe nos comentários também :)²

Grande abraço e obrigado por ter chegado até aqui.

Até o próximo post.

Link do repositório: https://github.com/LeonardoPrange/MediatorSimples

Repositório do MediatR: https://github.com/jbogard/MediatR

Referencias

Cooper, James W. (2002). C# Design Patterns: A Tutorial.

Farinelli, Fernanda. (2007). Conceitos básicos de programação orientada a objetos.

Gamma, et al. Padrões de Projeto: soluções reutilizáveis de software orientado a objetos, Bookman, 2007.

https://github.com/jbogard/MediatR/wiki

--

--

Leo Prange
Qualyteam Engineering

Consultor de desenvolvimento de software @Thoughtworks