C#
- Padrões Comportamentais
Gof - Chain of Responsability
![]() |
Neste artigo vou apresentar o padrão comportamental Gof Chain of Responsability. |
O padrão
padrão de projeto comportamental Chain of Responsabilitiy
evita acoplar o remetente de uma solicitação a seu receptor, dando a mais
de um objeto a chance de lidar com a solicitação.
Este padrão
encadeia os objetos receptores e passa a solicitação ao longo da cadeia até que
um objeto a trate. Dessa forma o padrão Chain of
Responsabilitity cria uma cadeia de objetos receptores para uma
determinada solicitação onde cada receptor contém uma referência a outro
receptor.
Se um receptor não puder lidar com a solicitação, ele passará a mesma
solicitação para o próximo receptor e assim por diante.
Assim, este padrão atua Simplificando as interconexões de objetos e evitando a
dependência entre um objeto receptor e um objeto solicitante ou destinatário.
Neste padrão, você
forma uma cadeia de objetos onde cada objeto da cadeia pode manipular um tipo
específico de pedido.
No exemplo desta figura temos uma cadeia com n números de manipuladores
Se um objeto não pode tratar a solicitação ele passa o solicitação para o
próximo objeto na cadeia. Este processo pode continuar até o final da
cadeia.
Este tipo de mecanismo de tratamento de solicitações oferece flexibilidade para
adicionar um novo objeto de processamento (manipulador) no final da
cadeia.
Um exemplo prático de aplicação deste padrão é o pipeline ASP.NET Core
, que usa middlewares, e onde o padrão cadeia de responsabilidade foi
aproveitado para fornecer um modelo de programação extensível.
Exemplo de uso
Vamos entender o
funcionamento deste padrão com o seguinte exemplo.
Na figura cima temos :
- Um manipulador para notas de 100
- Um manipulador para notas de 50
- Um manipulador para notas de 20
- Um manipulador para notas de 10
- Um manipulador para notas de 5
O cliente pretende realizar um saque de R$ 470.00 do caixa eletrônico e quer receber : 2 notas de 100, 4 notas de 50, 3 notas de 20 e 2 notas de 5.
- Para atender o
pedido do cliente o Caixa Eletrônico recebe o pedido, autoriza e envia uma
solicitação ao primeiro manipulador de notas, o HandlerCem,
que irá separar 2 notas de 100,00;
- A seguir o manipulador HandlerCem vai enviar a
solicitação ao manipulador HandlerCincoenta que vai
receber o pedido
e vai verificar o valor restante e vai separar 4 notas de 50,00;
- A seguir o manipulador HandlerCincoenta vai
encaminhar a solicitação para o próximo manipulador - o
HandlerVinte -
que vai verificar o valor que falta e vai separar 3 notas de 20,00;
- A seguir o manipulador HanclerCincoenta encaminha
a solicitação para o próximo manipulador - HandlerDez
- que verificar o valor restante que é 10 Reais, mas como o cliente quer receber
duas notas de 5 - este manipulador não pode processar esta solicitação e
encaminha o pedido para o próximo manipulador -
HandlerCinco;
- O manipulador HandlerCInco calcula o valor
restante que é 10 e vai separar as duas notas de 5.00;
Assim ele vai encerrar o tratamento do pedido e vai fornecer o valor de R$
470,00 conforme solicitado pelo cliente.
Este é um exemplo bem claro de como o padrão Chain of
Responsability funciona. Ele permite que você transmita uma solicitação
por meio de uma série de objetos manipuladores, e, uma cadeia de objetos é
formada, onde um objeto recebe a solicitação, faz algum trabalho específico e
passa a solicitação para o próximo objeto na cadeia.
No final da cadeia, a solicitação será tratada com sucesso ou vai gerar uma
exceção aqui podemos definir o comportamento.
Diagrama UML
O diagrama UML do padrão Chain of Responsbility segundo o Gof apresenta os seguintes participantes
1- Client - Que Inicia a solicitação para um objeto
ConcreteHandler na cadeia de responsabilidade.
Esta classe gera a solicitação e passa para o primeiro manipulador na cadeia
2- Handler - Define uma forma para tratar as
solicitações. Pode ser uma interface ou classe abstrata que contém :
- Um membro que referencia o próximo manipulador na cadeia;
- Um método que deve ser implementado para manipular a solicitação ou passá-la
para o próximo objeto;
Ela faz referência apenas ao próximo manipulador na cadeia e não sabe nada sobre
o restante dos manipuladores.
3 - ConcreteHandlers - São as classes concretas que
representam os manipuladores ConcreteHandler1 e 2 e
tratam a solicitação de sua responsabilidade ou enviam a solicitação para o seu
sucessor. Se a classe puder tratar a solicitação ela o faz, caso contrario
envia a solicitação para o próximo manipulador.
Quando podemos usar o padrão
Podemos usar o
padrão cadeia de responsabilidade nos seguintes cenários :
- Quando você tem mais de um manipulador para uma solicitação;
- Quando você tem motivos pelos quais um gerenciador deve passar uma solicitação
para outro na cadeia;
- Quando você tem um conjunto de manipuladores que variam de forma dinâmica;
- Quando você deseja manter a flexibilidade na atribuição de solicitações a
manipuladores;
- Quando você tem uma "cadeia" muito lógica de manipuladores que devem ser
executados em ordem todas as vezes;
Vantagens do padrão
Como vantagens deste padrão temos que :
-Você pode
controlar a ordem de tratamento de solicitações.
-Você pode desacoplar classes que invocam operações de classes que executam
operações. (Aqui estamos usamos o principio da responsabilidade única SRP)
-Você pode introduzir novos manipuladores no aplicativo sem quebrar o código do
cliente existente.(Aqui estamos usando o principio Open/Close)
- O padrão permite adicionar ou remover responsabilidades dinamicamente,
alterando os membros ou a ordem da cadeia.
Desvantagem
Como desvantagens
deste padrão temos que :
- Algumas solicitações podem não serem tratadas, e assim não há garantia de que
a solicitação será tratada porque você pode chegar ao final da cadeia, mas não
encontrou nenhum receptor para lidar com a solicitação;
- Outra desvantagem é que também podemos ter uma certa dificuldade em observar
as características em tempo de execução(ou realizar a depuração) devido o
encadeamento das chamadas entre os manipuladores;
Aplicação prática do padrão
Vejamos a seguir
um exemplo pratico de implementação do padrão Chain of
Responsability.
Vamos supor que temos uma empresa na área de TI onde temos a seguinte
hierarquia:
A empresa possui
equipes com : Desenvolvedor, Gerente de Projeto,
Supervisor Equipe, e, o Departamento de RH.
Assim, na hierarquia para assuntos relacionados a férias ou licença remunerada,
temos a seguinte fluxo:
- O Desenvolvedor se reporta ao Gerente de projeto;
- O Gerente de projeto se reporta ao Supervisor de Equipe;
- O Supervisor da Equipe se reporta ao Setor de RH da empresa;
A titulo de
exercício vamos considerar a seguinte política de concessão de licenças
remuneradas :
- O Gerente de projeto tem alçada para aprovar até 5 dias de licença;
- O Supervisor da equipe tem alçada para aprovar até 15 dias de licença;
- O Setor de RH tem alçada para aprovar até 30 dias de licença;
Então, se um Desenvolvedor solicitar 25 dias de licença, ele envia o seu
pedido ao Gerente de Projeto.
O Gerente de Projeto vai verificar se ele pode aprovar o pedido ou não, e, como ele só tem alçada até 10 dias ele vai repassar o pedido para o Supervisor da Equipe.
O Supervisor da
Equipe vai verificar o pedido e ver se ele pode aprovar; como ele somente pode
aprovar até 10 dias de licença,
ele vai repassar o pedido para o setor de RH.
O Setor de RH vai receber o pedido, vai verificar se esta dentro da sua alçada
de 30 dias, e, vai liberar o pedido de licença solicitado encerrando assim o
processamento do pedido.
Aqui o que deve
ficar bem claro, é que uma vez que a solicitação for tratada por qualquer
manipulador ele não deve encaminhar essa solicitação para o seu sucessor.
Para implementar esse cenário vamos ter que criar uma cadeia de autorização
usando o padrão Chain of Responsability.
Implementação prática
Levando em conta este cenário e estas considerações vamos implementar o padrão Chain of Responsability usando uma aplicação Console .NET Core (.NET 5.0) criada no VS 2019 Community.
A seguir temos o diagrama de classes obtido a partir do VS 2019 na implementação do padrão:
1 - Autorizador: É uma classe abstrata que representa o Handler e onde definimos o método ProximoAutorizador para encaminhar a solicitação para o próximo Autorizador da cadeia de autorização. Nesta classe definimos o método abstrato AutorizarLicenca que vai tratar as solicitações;
2 -
GerenteProjeto: É uma classe concreta
que representa o ConcreteHandler que herda de
Autorizador e sobrescreve o método
AutorizarLicenca. Aqui definimos a lógica
para autorização das licenças, verificamos se a solicitação poderá ser tratada
aqui
caso contrário encaminhamos a solicitação para o próximo autorizador;
autorizador
3- SupervisorEquipe e SetorRH : São classes concretas que representam o ConcreteHandler e possuem a mesma implementação é lógica definida em GerenteProjeto;
5- Program - A classe Program representa o Client e Inicia a solicitação para um objeto ConcreteHandler na cadeia de responsabilidade. Esta classe gera a solicitação e passa para o primeiro manipulador na cadeia;
A seguir temos o código usado na implementação:
1- A classe abstrata Autorizador(Handler)
public abstract class Autorizador
{
protected Autorizador _autorizador;
public void ProximoAutorizador(Autorizador autorizador)
{
_autorizador = autorizador;
}
public abstract void AutorizarLicenca(string nome, int dias);
}
|
2- Classe GerenteProjeto (ConcreteHandler)
using System;
namespace ChainResponsability1
{
public class GerenteProjeto : Autorizador
{
private int ALCADA_DIAS = 5;
public override void AutorizarLicenca(string nome, int dias)
{
if (dias <= ALCADA_DIAS)
{
AprovarLicenca(nome, dias);
}
else
{
_autorizador?.AutorizarLicenca(nome, dias);
}
}
private void AprovarLicenca(string nome, int dias)
{
Console.WriteLine($"O Gerente de Projeto aprovou {dias} dias " +
$"de licença remunerada para : {nome}\n");
}
}
}
|
3- Classe SupervisorEquipe (ConcreteHandler)
using System;
namespace ChainResponsability1
{
public class SupervisorEquipe : Autorizador
{
private int ALCADA_DIAS = 15;
public override void AutorizarLicenca(string nome, int dias)
{
if (dias <= ALCADA_DIAS)
{
AprovarLicenca(nome, dias);
}
else
{
_autorizador?.AutorizarLicenca(nome, dias);
}
}
private void AprovarLicenca(string nome, int dias)
{
Console.WriteLine($"O Supervisor da Equipe aprovou {dias} dias " +
$"de licença remunerada para : {nome}\n");
}
}
}
|
4- Classe SetorRH(ConcreteHandler)
using System;
namespace ChainResponsability1
{
public class SetorRH : Autorizador
{
private int ALCADA_DIAS = 30;
public override void AutorizarLicenca(string nome, int dias)
{
if (dias <= ALCADA_DIAS)
{
AprovarLicenca(nome, dias);
}
else
{
Console.WriteLine($"\n Não foi possível autorizar a licença " +
$"de {dias} dias para {nome}.\n *** Comunique a Diretoria ***\n");
}
}
private void AprovarLicenca(string nome, int dias)
{
Console.WriteLine($"O setor de RH aprovou {dias} dias " +
$"de licença remunerada para : {nome}\n");
}
}
}
|
5- Program
|
A execução do projeto irá apresentar o seguinte resultado:
Embora essa implementação seja bem simples e possa ser melhorada em muitos
aspectos ela exemplifica bem o funcionamento do padrão
Chain of Responsability.
O mecanismo de tratamento de exceções na plataforma .NET é um exemplo onde o
padrão da cadeia de responsabilidade foi alavancado.
Sabemos que podemos ter vários blocos catch em um código de bloco try-catch. Aqui, cada bloco catch é uma espécie de processador para processar essa exceção em particular.
Portanto, quando
ocorre uma exceção no bloco try, é enviada para o
primeiro bloco catch para processar. Se o bloco catch não for capaz de
processá-lo, ele encaminha a solicitação para o próximo objeto na cadeia, ou
seja, próximo bloco catch. Se mesmo o último
bloco catch não for capaz de processá-lo, a exceção
é lançada fora da cadeia para o programa de chamada.
Pegue o código
do projeto aqui :
ChainResponsability1.zip
"Cheguemos, pois, com confiança ao
trono da graça, para que possamos alcançar misericórdia e achar graça, a fim de
sermos ajudados em tempo oportuno."
Hebreus 4:16
Referências:
NET - Unit of Work - Padrão Unidade de ...
NET - O padrão de projeto Decorator
NET - Padrão de Projeto Builder
C# - O Padrão Strategy (revisitado)
NET - O padrão de projeto Command
NET - Apresentando o padrão Repository