[Delphi] Design Patterns GoF – Chain of Responsibility

E aí, pessoal, tudo bem?
O artigo de hoje marca o início dos padrões de projetos Comportamentais. Recebem este nome por propor e recomendar soluções que envolvam interações entre objetos, de forma que, mesmo que exista essa interação, eles não dependam fortemente um do outro, ou seja, fiquem fracamente acoplados. O primeiro deste padrões, muito fácil de compreender, é o Chain of Responsibility. Acompanhe o artigo!

Introdução

Uma das premissas mais importantes na elaboração da arquitetura de um software é manter o baixo acoplamento e a alta coesão. O primeiro requisito refere-se à eliminação de fortes dependências entre classes, enquanto o segundo empenha a responsabilidade única de cada classe, respondendo a princípio chamado Single Responsibility. Uma arquitetura com baixo acoplamento e alta coesão, portanto, significa que as classes são bem delimitadas e cada uma assume apenas uma função exclusiva no sistema.

O padrão de projeto Chain of Responsibility representa uma solução para a redução de dependências entre classes. O maior propósito é permitir que mensagens (ou dados) naveguem entre diferentes objetos dentro de uma hierarquia (ou cadeia) até que um desses deles tenha a capacidade de assumi-la, ou melhor, processá-la, mas com um detalhe importante: nessa hierarquia, cada objeto não conhece os detalhes do outro.

O exemplo mais clássico encontrado na internet sobre este padrão de projeto refere-se à aprovação de um orçamento. Neste exemplo, o orçamento, que é a “mensagem”, é enviado primeiro para o gerente, que possui permissão para aprovar valores até 10 mil reais. Se o valor do pedido for maior, até 20 mil reais, é enviado para o próximo cargo na hierarquia que, neste caso, é o diretor. E finalmente, caso seja maior que 20 mil reais, o orçamento é enviado para o presidente. Observe que, se forem emitidos três pedidos nos valores de 6, 18 e 25 mil reais cada um, a aprovação será feita por pessoas diferentes.

Lembram-se que mencionei o baixo acoplamento? Pois bem, no contexto do Chain of Responsibility, cada “elo da corrente”, ou seja, cada objeto, não conhece os outros participantes da hierarquia, mas sabe que eles existem. Em termos mais técnicos, cada objeto sabe que o seu superior (ou sucessor) implementa uma determinada Interface, porém, não conhece a sua identidade. Esse cenário permite que não existam fortes dependências entre classes, de modo que a cadeia de responsabilidades possa ser alterada a qualquer momento sem impactar na funcionalidade existente.

Considere a ilustração abaixo:

Exemplo de mensagem percorrendo os elos da cadeira de responsabilidades

Se o 2º elo for removido, o sucessor do 1º elo passará a ser o 3º. A mensagem, por sua vez, não sofre impacto com a alteração da corrente e continuará sendo transmitida naturalmente pela hierarquia até que seja processada por algum objeto.

Vamos desenvolver um exemplo prático para solidificar a definição deste padrão de projeto?

Exemplo de codificação do Chain of Responsibility

Temos a seguinte situação: o nosso sistema deve disponibilizar uma funcionalidade de importação de dados para evitar que os usuários tenham que inclui-los manualmente. No entanto, cada cliente que utiliza o sistema trabalha com um formato diferente de arquivo, conforme apresentado abaixo:

• CSV

• XML

• JSON

A nossa proposta é criar uma cadeia de responsabilidades que receba o arquivo de importação e processe os dados, independente do formato. Cada elo da corrente será o Parser de um dos formatos apresentados. Se o elo atual identificar que não consegue processar o formato (mensagem), o arquivo é delegado para o próximo elo.

Uma nota importante aqui: a hierarquia que discutimos até agora não precisa necessariamente ser vertical. No exemplo deste artigo, você verá que o fluxo da hierarquia é horizontal. O maior objetivo dessa hierarquia é separar as responsabilidades de cada classe e mantê-las desacopladas, mas, ao mesmo tempo, prover um mecanismo eficiente de processamento de dados.

Interface Handler e classes Concrete Handlers

A primeira etapa é criar a Interface que será comum para todos os “importadores de dados”, chamada Handler:

Os Concrete Handlers são as implementações concretas da Interface Handler. No nosso caso, cada classe será responsável pelo processamento de um formato específico de arquivo.

• CSV

• XML

• JSON

Em ação!

Nossa codificação já está praticamente pronta. O Client será o botão “Processar Inclusão” da tela abaixo:

Formulário de exemplo de demonstração do Chain of Responsibility

Neste botão, faremos a última etapa – e mais importante – do padrão de projeto: “montar” a nossa cadeia da forma como desejamos. Na codificação abaixo, configurei a hierarquia CSV -> XML -> JSON:

Perfeito! Os arquivos com formato CSV, XML e JSON serão processados pelo 1º, 2º e 3º elos, respectivamente. Observe que, no caso do JSON, a mensagem passará pelos dois primeiros elos da cadeia, mas não será processada, já que não é “interpretável” pelas classes.

Conclusão

Bom, agora podemos enviar a mesma versão da aplicação para todos os clientes com segurança. A importação de dados funcionará com sucesso, independente de qual formato de arquivo de importação que o cliente trabalha.

Neste exemplo, podemos identificar mais algumas vantagens. Em primeiro lugar, podemos adicionar ou remover elos da cadeia sem quebrar a funcionalidade, bem como trocá-los de ordem. Em segundo lugar, cada classe não possui vínculo com as outras, evitando a Dependência Magnética quando uma alteração é necessária em uma delas. Por último, atendemos às características de baixo acoplamento e alta coesão! 🙂

Como de costume, disponibilizei o projeto de exemplo deste artigo para download. Só há uma pequena modificação: codifiquei mais um Parser para processar arquivos TXT. Além disso,  adicionei 4 arquivos com dados dentro do diretório da aplicação, um em cada formato, para facilitar os testes.

 

Esse foi bem fácil, né?
Preparem-se para o próximo Design Pattern! Até lá!


 

André Celestino