Arquitetura e Desenvolvimento de software — Parte 14 — Chain of Responsability

E aí pessoal, tudo bem?

Neste artigo, vamos dar início ao estudo dos padrões comportamentais, continuando a série sobre design patterns, começando pelo padrão Chain of Responsability.


Padrões Comportamentais

Antes de falarmos detalhadamente sobre o Chain of Responsability, vamos entender o que é o grupo de padrões comportamentais.

Padrões comportamentais tratam de algoritmos que focam nas relações entre os objetos, fazendo com que essas entidades se comuniquem mais facilmente e flexivelmente.


Chain Of Responsability

Chain of Responsibility tem como principal função evitar a dependência entre um objeto receptor e um objeto solicitante. Consiste em uma série de objetos receptores e de objetos de solicitação, onde cada objeto de solicitação possui uma lógica interna que separa quais são tipos de objetos receptores que podem ser manipulados. O restante é passado para o próximo objeto de solicitação da cadeia.

Devido à isso, é um padrão que utiliza a ideia de baixo acoplamento por permitir que outros objetos da cadeia tenham a oportunidade de tratar uma solicitação.

Ok, acho que fui um pouco rápido demais na explicação…

Explicando melhor

Vamos pensar no seguinte exemplo:

  • Temos uma empresa, onde cada cargo de gestão pode solicitar a compra de materiais de escritório e outras coisas;
  • Cada cargo tem um limite de gastos. Caso a compra ultrapasse o limite de gastos do cargo, a responsabilidade / aprovação da compra deve ser passada para o próximo nível.
  • Temos os seguintes cargos: Supervisor, Gerente, Diretor e Presidente.

Para implementar isso, imagine que temos que verificar cada cargo, seu limite, se a compra passa esse limite para então instanciar outro cargo acima. Já imagina a quantidade de IF’s que podemos ter, tornando nosso código mais complexo de manter.

Utilizando o Chain of Responsability, solucionamos o problema acima da seguinte forma:

  • Iremos criar uma classe base, representando um Gestor de qualquer cargo, com as propriedades de Limite, Cargo e, se aplicável, uma referência para o superior, que também é um Gestor.
  • Nesta classe base, criaremos um método para processar a compra. Esse método verifica se o valor da compra ultrapassa o limite do gestor. Caso ultrapasse, chama o processamento da instância do superior, ou se não ultrapassar o limite, processa a compra.
  • Para cada cargo específico, criamos uma classe específica onde, no construtor, informamos o limite e nome daquele cargo, assim como seu superior.

Notem que pela abordagem acima, iremos centralizar a regra de processamento na classe gestor. Ou seja, independente de quantos cargos mais possamos criar, a regra está ali, em um único local e de fácil manutenção.

Dado que cada cargo tem uma referência a seu superior e possui a lógica da classe base Gestor, entendemos que cada um vai ser responsável por chamar seu superior caso o valor de compra ultrapasse seu limite. Ou seja, a primeira chamada de aprovação da compra sera encadeada (Chain) de acordo com a necessidade e responsabilidade (Responsability) de cada cargo.

Vamos ver isso melhor codificando nosso exemplo:

Notem que cada cargo, ou classe, criado não tem nenhuma funcionalidade implementada, está toda na classe base. E se fosse necessário, ainda poderíamos sobreescrever o processamento da classe base.

Nosso código possui apenas um IF (com exceção do While utilizado no console, mas ali é parte da “View” de nosso exemplo) que valida a regra de limite.

Vamos a ficha resumo do pattern onde iremos detalhar os pontos que faltam, incluindo o diagrama UML, e ver um exemplo extra, desta vez em Java.

Ficha Resumo

  • Nome: Chain Of Responsability;
  • Objetivo / intenção: Evitar o acoplamento do remetente de uma solicitação ao seu receptor, ao dar a mais de um objeto a oportunidade de tratar a solicitação. Encadear os objetos receptores, passando a solicitação ao longo da cadeia até que um objeto a trate;
  • Motivação: Quando temos uma determinada regra / processo que tem uma dependência entre objetos em hierarquia ou sequência, utilizamos o pattern de modo a encadear o processamento entre varios objetos, onde cada um recebe a solicitação, trata, e se necessário, envia a um novo objeto para continuar o processamento, fornecendo uma maneira de tomar decisões com um fraco acoplamento;
  • Aplicabilidade: Utilizamos o pattern quando vemos que mais de um objeto pode tratar uma solicitação e o objeto que a tratará não é conhecido inicialmente, oobjeto que trata a solicitação deve ser escolhido automaticamente, deve-se emitir uma solicitação para um dentre vários objetos, sem especificar explicitamente o receptor ou o conjunto de objetos que pode tratar uma solicitação deveria ser especificado dinamicamente. São situações mais comuns que icentivam a aplicação do pattern;
  • Estrutura: No desenho abaixo, temos um exemplo UML de aplicação do pattern, já descrito no início do artigo com a responsabilidade de cada parte do diagrama:
Diagrama UML, exemplificando nosso handler e as devidas implementações dele, com chamada a seu sucessor.
  • Consequências: Vimos pelo exemplo que o padrão Chain of Responsibility fornece uma maneira de tomar decisões com um fraco acoplamento. Perceba que a estrutura de cadeia não possui qualquer informação sobre as classes que compõem a cadeia, da mesma forma, uma classe da cadeia não tem nenhuma noção sobre o formato da estrutura ou sobre elementos nela inseridos. Mas um ponto de atenção é que precisamos garantir que as chamadas sejam realmente respondidas. No exemplo foi feita uma verificação para saber se o próximo elemento é nulo, para evitar uma acesso ilegal. Mas esta é uma solução para este problema específico. Cada problema exige o seu próprio cuidado;
  • Implementações: Abaixo temos um outro exemplo de código em Java para o pattern:
  • Usos conhecidos: Utilizamos em sistemas que possuem rotinas de tomada de decisão complexas e que normalmente precisem respeitar uma hierarquia ou fluxo de processamento, sendo que encadeamos a requisição de processamento por N objetos, um chamando o próximo e assim sucessivamente até que seja concluído o processamento, com sucesso ou falha. Um outro uso bem conhecido: já ouviu falar de Middlewares? Aguardem pois teremos um artigo dedicado a este assunto.
  • Padrões relacionados: Chain of Responsibility, Command, Mediator, e Observer são como endereços, podendo ser separados entre remetentes e destinatários, mas com resultados e desafios diferentes. O Chain of Responsibility passa um pedido ao remetente junto com uma cadeia de potenciais destinatários. Podemos inclusive pode usar o padrão Command para representar pedidos como objetos. Frequentemente também aplicamos o padrão Composite, onde temos uma hierarquia dos objetos, como inclusive foi feito no exemplo em C#, utilizando uma classe abstrata, que poderia ter mais classes na hierarquia, compondo diferentes processos de encadeamento.

Concluindo

Hoje aprendemos sobre o pattern Chain Of Responsability, que facilita nosso trabalho quando temos uma regra comum aplicada a uma cadeia de entidades com restrições ou detalhes extras para a regra, que irão processar em forma de uma cadeia de chamadas esta regra, conforme necessidade ou restrições de cada entidade na cadeia.

Por hoje deixo este material para vocês na próxima parte, vamos conhecer o Iterator, dando continuidade ao grupo de patterns comportamentais.

Para quem perdeu a série desde o início, segue o link para a primeira parte, onde listo todos os patterns que vamos abordar, cada um com o link correspondente, servindo de índice:

Um abraço a todos e até a próxima!