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

Como Verificar se um Objeto Existe (Delphi)

Em alguns momentos surge a necessidade de verificar se um determinado objeto existe, ou seja se já foi criado, principalmente quando se trabalha com criação dinâmica em tempo de execução, então vamos ao exemplo: - Vamos criar uma variável, um vetor do tipo caixa de texto: var Minha_caixa : array of TEdit; - Em seguida definir o tamanho desse vetor, no caso será dez: setLength(Minha_caixa, 10) - Agora iremos criar nossa caixa de texto: // lembrando que o vetor inicia em zero // logo o índice final é o tamanho total - 1 for vl_i := 0 to Length(Minha_caixa) -1 do begin Minha_caixa[vl_i] := TEdit.Create(self); with Minha_caixa[vl_i] do begin Parent := Self; Name := 'Caixa_N'+IntToStr(vl_i); Text := 'Esta é a '+IntToStr(vl_i)+' º caixa !'; ReadOnly := true; Height := 21; Width := ...

Iniciar e Parar Serviços do Windows (Delphi)

Em certas ocasiões nos deparamos com a necessidade de manipular determinadas atividades do SO, como iniciar ou parar um banco de dados, ou qualquer outro serviço que esteja funcionando no momento. Segue abaixo um código que encontrei na Internet para tal finalidade (não me recordo à fonte, assim que eu a encontrar colocarei). Iniciar Serviço: uses WinSvc; // // start service // // return TRUE if successful // // sMachine: //   machine name, ie: \SERVER //   empty = local machine // // sService //   service name, ie: Alerter // function ServiceStart(   sMachine,   sService : string ) : boolean; var   //   // service control   // manager handle   schm,   //   // service handle   schs   : SC_Handle;   //   // service status   ss     : TServiceStatus;   //   // te...

Funções de Data e Hora (Delphi)

É muito comum nos depararmos no dia a dia com a necessidade de manipular datas e horas, seja para um calculo de permanência, dias de atraso enfim, é praticamente escapar de alguma situação que necessite desse tipo de controle. Assim como a necessidade e se utilizar algum recurso para manipular as datas e horas de alguma maneira e freqüente, as duvidas de como o faze-lo também é, basta um breve olhar em qualquer fórum especializado e lá está, alguma duvida relacionada, por isso decidi falar um pouco sobre uma unit muito poderosa chamada DateUtils para a manipulação de data e hora, com um grande numero de métodos e classes que facilitam a vida de qualquer um. Alguns exemplos: CompareDate(constA, B: TDateTime): TValueRelationship; Compara apenas a data de dois valores (do tipo TDateTime) retornando: LessThanValue O primeiro valor é menor que o segundo EqualsValue Os valores são iguais GreaterThanValue O primeiro valor é maior que o segundo CompareDateTime(const A, B: TD...