Pular para o conteúdo principal

C# - Design Patterns - Chain of Responsibility

Olá a  todos! Seguindo a proposta de padrões de projeto com C#, falaremos hoje sobre o padrão comportamental Chain of Responsibility. Para ver outros padrões que já visitamos por aqui consulte esse link. Esse tema foi tratado de forma brilhante aqui.


Chain of Responsibility


O padrão de projeto Chain of Responsibility é um padrão de design comportamental que permite passar uma solicitação ao longo de uma cadeia de objetos até que um deles a trate. Cada objeto na cadeia tem a chance de tratar a solicitação. É útil quando há múltiplos objetos que podem responder a uma solicitação e você não sabe antecipadamente qual objeto será o responsável.


No C#, a implementação deste padrão envolve a definição de uma interface para as solicitações, criação de classes concretas para tratar as solicitações e definição de uma lógica de encadeamento entre essas classes. Ao receber uma solicitação, cada objeto na cadeia verifica se pode tratá-la e, se não puder, passa a solicitação para o próximo objeto na cadeia.


Quando podemos usar?


O Chain of Responsibility deve ser usado em situações onde:

  • Existe múltiplos objetos que podem responder a uma solicitação e você não sabe antecipadamente qual objeto será o responsável.
  • Você quer evitar um acoplamento rígido entre as solicitações e seus tratadores, permitindo que o número e o tipo de tratadores sejam especificados dinamicamente.
  • Você quer evitar o envio de uma solicitação a objetos desnecessários, tornando o processo de tratamento mais eficiente.
  • Você deseja que vários objetos tenham a chance de tratar a solicitação, sem que seja necessário especificar explicitamente a ordem em que os objetos serão verificados.

Além disso, o Chain of Responsibility é especialmente útil quando existe múltiplos tratadores que podem ser adicionados ou removidos ao longo do tempo, sem que seja necessário alterar a classe que envia as solicitações.


Exemplo


No exemplo abaixo, a interface IRequest representa as solicitações e é implementada pela classe concreta PurchaseRequest. Esta classe representa uma solicitação de compra.


As três classes concretas de tratadores Director, VicePresident e President representam três níveis de aprovação para a solicitação de compra. Cada classe herda da classe abstrata Approver e implementa o método ProcessRequest para tratar a solicitação. O método verifica se o valor da solicitação é menor do que o seu limite de aprovação e, se for, aprova a solicitação. Se não puder aprovar a solicitação, ele passa para o próximo tratador na cadeia, se houver.


Na classe Program, criamos a cadeia de tratadores, definindo qual tratador é o sucessor de outro tratador na cadeia. Em seguida, enviamos três solicitações de compra diferentes para a cadeia, onde são processadas pelos tratadores na ordem correta, até que seja encontrado o tratador responsável ou a cadeia chegue ao fim.

    
        using System;

        // Define a interface para as solicitações
        interface IRequest
        {
            int Amount { get; set; }
        }
        
        // Define a classe concreta para as solicitações
        class PurchaseRequest : IRequest
        {
            public int Amount { get; set; }
            public PurchaseRequest(int amount)
            {
                this.Amount = amount;
            }
        }
        
        // Define a classe abstrata para os tratadores
        abstract class Approver
        {
            protected Approver successor;
            public void SetSuccessor(Approver successor)
            {
                this.successor = successor;
            }
            public abstract void ProcessRequest(IRequest request);
        }
        
        // Define as classes concretas para os tratadores
        class Director : Approver
        {
            public override void ProcessRequest(IRequest request)
            {
                if (request.Amount < 10000)
                {
                    Console.WriteLine("A solicitação de compra de R$" + request.Amount + " foi aprovada pelo Diretor.");
                }
                else if (successor != null)
                {
                    successor.ProcessRequest(request);
                }
            }
        }
        
        class VicePresident : Approver
        {
            public override void ProcessRequest(IRequest request)
            {
                if (request.Amount < 25000)
                {
                    Console.WriteLine("A solicitação de compra de R$" + request.Amount + " foi aprovada pelo Vice-Presidente.");
                }
                else if (successor != null)
                {
                    successor.ProcessRequest(request);
                }
            }
        }
        
        class President : Approver
        {
            public override void ProcessRequest(IRequest request)
            {
                if (request.Amount >= 25000)
                {
                    Console.WriteLine("A solicitação de compra de R$" + request.Amount + " foi aprovada pelo Presidente.");
                }
            }
        }
        
        class Program
        {
            static void Main(string[] args)
            {
                // Cria a cadeia de tratadores
                Approver director = new Director();
                Approver vicePresident = new VicePresident();
                Approver president = new President();
        
                director.SetSuccessor(vicePresident);
                vicePresident.SetSuccessor(president);
        
                // Envia as solicitações
                IRequest request = new PurchaseRequest(2000);
                director.ProcessRequest(request);
        
                request = new PurchaseRequest(20000);
                director.ProcessRequest(request);
        
                request = new PurchaseRequest(50000);
                director.ProcessRequest(request);
        
                Console.ReadKey();
            }
        }        
    

Conclusão

Como podemos perceber o padrão que abordamos aqui permite que vários objetos tentem responder a uma solicitação sem que a classe que envia essa solicitação saiba qual objeto é o responsável, criando uma cadeia de tratadores, onde cada tratador tem a oportunidade de processar a solicitação e passá-la para o próximo tratador na cadeia, se não puder tratá-la.

Comentários

Mais visitadas

Aplicação Flask usando Nginx e Gunicorn

Aplicação Flask usando Nginx e Gunicorn Se você já desenvolveu uma aplicação Flask básica, sabe que o servidor de desenvolvimento embutido não é ideal para produção. Ele não é projetado para lidar com altos volumes de tráfego ou conexões simultâneas. Para tornar sua aplicação Flask mais robusta e pronta para produção, podemos usar o Gunicorn como servidor de aplicação e o Nginx como proxy reverso. Neste artigo, vamos adaptar o exemplo anterior ( Criando uma Aplicação CRUD com Flask, PostgreSQL e Docker ) para incluir o Nginx e o Gunicorn. O que são Nginx e Gunicorn? Gunicorn O Gunicorn (Green Unicorn) é um servidor de aplicação WSGI que roda aplicações Python como o Flask. Ele é eficiente e simples de configurar, lidando com múltiplas requisições ao mesmo tempo, algo que o servidor embutido do Flask não faz bem. Nginx O Nginx é um servidor web que atua como um proxy reverso. Ele recebe requisições HTTP e as encaminha ao Gunicorn. Além disso, o Nginx pode: Servir arquivos ...

Array no PL/SQL (Oracle)

Trabalhar com estruturas indexadas pode nos poupar muito trabalho, deixar o código mais limpo e reutilizável, pois bem vamos dar um exemplo de como fazer isso no PL/SQL. Criaremos um tipo table que seja capaz de armazenar nomes de uma tabela de funcionários de forma indexada, e em seguida mostraremos o que foi armazenado, segue o código: 1: declare 2: -- tipo tabela contendo apenas texto e indexado 3: type TipoNomFunc is table of varchar 2(200) index by binary_integer; 4: -- variável do nosso tipo (como nosso tipo é indexado ele funcionará como um array) 5: func TipoNomFunc; 6: -- indice para loop 7: indice number := 1; 8: -- 9: begin 10: -- 11: -- cursor para nossa tabela de funcionarios 12: for emps in ( 13: select * 14: from funcionarios 15: ) 16: loop 17: -- colocamos o nome do funcionario em nosso "vetor" 18: func(indice) := emps.nom_funcionario; 19: -- incrementamos o indice 20:...

Listar arquivos existentes em diretório (Delphi)

Mostraremos uma maneira simples e prática para listar o conteúdo de um diretório com a opção de incluir nessa listagem os arquivos de seus subdiretórios. No exemplo abaixo temos um Edit para receber o diretório a ser pesquisado um CheckBox para indicar se os subdiretórios entrarão na pesquisa um botão para efetuar a pesquisa e um Memo para listar os arquivos encontrados, no final um Edit que receberá o cálculo final (em bytes) da soma do tamanho dos arquivos. procedure TForm1.Button1Click(Sender: TObject); begin   tamanhoTotal := 0;   memLista.Lines.Clear;   ListarArquivos(edtDiretorio.Text, chkSub.Checked);   Edit1.Text := IntToStr( tamanhoTotal ); end; procedure TForm1.ListarArquivos(Diretorio: string; Sub:Boolean); var   F: TSearchRec;   Ret: Integer;   TempNome: string; begin   Ret := FindFirst(Diretorio+'\*.*', faAnyFile, F);   try     while Ret = 0 do ...