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 :

  1. Um cliente
  2. Um caixa eletrônico
  3. Cinco Manipuladores de Notas do caixa eletrônico :

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

using System;
namespace ChainResponsability1
{
    class Program
    {
        static void Main(string[] args)
        {
            GerenteProjeto gerenteProjeto = new GerenteProjeto();
            SupervisorEquipe supervisorEquipe = new SupervisorEquipe();
            SetorRH setorRH = new SetorRH();
            gerenteProjeto.ProximoAutorizador(supervisorEquipe);
            supervisorEquipe.ProximoAutorizador(setorRH);
            gerenteProjeto.AutorizarLicenca("Macoratti", 5);
            gerenteProjeto.AutorizarLicenca("Amanda", 14);
            gerenteProjeto.AutorizarLicenca("Benedito", 18);
            gerenteProjeto.AutorizarLicenca("Janice", 30);
            gerenteProjeto.AutorizarLicenca("Felipe", 50);
            Console.ReadKey();
        }
    }
}

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:


José Carlos Macoratti