Design Patterns – Parte 2 - Observer

by Cássio R Eskelsen 23. setembro 2008 07:54

Continuando a série Design Patterns, nessa parte 2 iremos ver o padrão Observer.

Suponha que você tenha uma linha de produção que é automaticamente alimentada de componentes por diversos robôs assim que entra um novo pedido. Cada robô solta um determinado número de peças por quantidade de pedido.

Uma primeira abordagem seria algo como abaixo:

using System;

namespace NadaObserver
{
    class Program
    {
        static void Main(string[] args)
        {
            LinhaProducao linha1 = new LinhaProducao();
            Robo wallE = new Robo("Wall-E",linha1, 1);
            Robo c3po = new Robo("C3PO", linha1, 10);
            Robo sonny = new Robo("Sonny", linha1, 20);
            Robo terminator = new Robo("Terminator 800", linha1, 1000);

            //entrou novo pedido de 10 peças
            Pedido ped = new Pedido(10);
            
            // põe o povo para trabalhar

            wallE.SuprirPecas(ped.QtPedido);
            c3po.SuprirPecas(ped.QtPedido);
            sonny.SuprirPecas(ped.QtPedido);

            Console.ReadLine();
            
        }
    }

    public class Pedido{

        public Pedido(int qtPedido)
        {
            this.QtPedido = qtPedido;
        }

        public int QtPedido { get; set; }
       
    }


    public class LinhaProducao{

        public LinhaProducao()
        {
        }

        public void ReceberPecasDoRobo(int qtPecas)
        {
            Console.WriteLine("Recebi {0} peças para montagem", qtPecas);
        }
    }

    public class Robo
    {
        private int _fatorPecasPorPedido = 1;
        
        private LinhaProducao _linhaDeProducao = null;

        public Robo(string nomeRobo, LinhaProducao linhaDeProducao, int fatorPecasPorPedido)
        {
            this.NomeRobo = nomeRobo;
            this._linhaDeProducao = linhaDeProducao;
            this._fatorPecasPorPedido = fatorPecasPorPedido;
        }

        public string NomeRobo { get; set; }

        public void SuprirPecas(int qtItensPedido)
        {
            this._linhaDeProducao.ReceberPecasDoRobo(qtItensPedido * this._fatorPecasPorPedido);
        }
 
    }
}

Além de deselegante, essa solução trás alguns problemas: a aplicação chamadora sempre deverá saber quais são os robôs que estão ligados à cada linha de produção.

É dela, também, a responsabilidade de "mandar" cada robô despejar na linha de produção a quantidade de peças para cada pedido, o que não garante que algum robô não seja esquecido (como propositadamente esqueci) de ser incluído no seguinte trecho de código:

wallE.SuprirPecas(ped.QtPedido);
c3po.SuprirPecas(ped.QtPedido);
sonny.SuprirPecas(ped.QtPedido);

Trazendo para o mundo real, seria equivalente a obrigar que o operador que traz os pedidos para a linha de produção saiba todos os robôs que estejam ligados à uma linha de produção e a cada novo pedido vá em cada robô apertar o botão que solta as peças nas linha de produção.  Ou seja, não preciso dizer que as chances de erro são grandes!

Uma forma de se resolver isso é criar uma dependência entre a Linha de Produção e os seus robôs de tal forma que a cada vez que entra um novo pedido, os robô alimentem automaticamente a linha de produção. É isso que o padrão observer faz por nós:

O padrão Observer cria uma dependência de um-para-muitos entre objetos de tal forma que quando o estado de um objeto é modificado, suas dependências sejam automaticamente notificadas.

Vamos refatorar o código aplicando o design-pattern Observer:

using System;
using System.Collections; 

namespace ObserverClassico
{
    class Program
    {
        static void Main(string[] args)
        {
            Robo wallE = new Robo("Wall-E", 1);
            Robo c3po = new Robo("C3PO", 10);
            Robo sonny = new Robo("Sonny",20);
            Robo terminator = new Robo("Terminator 800",1000);

            LinhaProducao linha = new LinhaProducao();
            linha.AtivarRobo(wallE);
            linha.AtivarRobo(c3po);
            linha.AtivarRobo(sonny);
            linha.AtivarRobo(terminator);

            linha.AdicionarPedido(new Pedido(10));

            Console.ReadLine();
            
        }
    }

    public class Pedido
    {
        public Pedido(int qtPedido)
        {
            this.QtPedido = qtPedido;
        }

        public int QtPedido { get; set; }

    }


    public class LinhaProducao
    {
        /// <summary>
        /// Estou usando ArrayList para que o exemplo seja entendível por quem
        /// não programa em .Net e não tem coleções genéricas
        /// </summary>
        public ArrayList pedidos;

        public ArrayList robosDaLinha;

        public LinhaProducao()
        {
            pedidos = new ArrayList();
            robosDaLinha = new ArrayList();
        }

        /// <summary>
        /// Liga um robô em uma linha de produção
        /// </summary>
        /// <param name="robo"></param>
        public void AtivarRobo(Robo robo)
        {
            robosDaLinha.Add(robo);
        }

        /// <summary>
        /// Retira um robo da lista de "observadores"
        /// </summary>
        /// <param name="robo"></param>
        public void DesativarRobo(Robo robo)
        {
            if (this.robosDaLinha.Contains(robo))
            {
                this.robosDaLinha.Remove(robo);
            }
        }

        /// <summary>
        /// Dá entrada de um novo pedido na linha de produção
        /// </summary>
        /// <param name="ped"></param>
        public void AdicionarPedido(Pedido ped)
        {
            pedidos.Add(ped);

            //notifica todos os robôs que eles preisam trabalhar!
            foreach (Robo robo in robosDaLinha)
            {
                robo.SuprirPecas(this, ped.QtPedido);
            }
        }

        public void ReceberPecasDoRobo(int qtPecas)
        {
            Console.WriteLine("Recebi {0} peças para montagem", qtPecas);
        }
    }

    public class Robo 
    {       

        private int _fatorPecasPorPedido = 1;        
       
        public Robo(string nomeRobo, int fatorPecasPorPedido)
        {
            this.NomeRobo = nomeRobo;
            this._fatorPecasPorPedido = fatorPecasPorPedido;
        }

        public string NomeRobo { get; set; }

        public void SuprirPecas(LinhaProducao linhaDeProducao, int qtItensPedido)
        {
            linhaDeProducao.ReceberPecasDoRobo(qtItensPedido * this._fatorPecasPorPedido);
        } 
 
    }
}

Perceba que agora os robôs são "amarrados" à linha de produção e cada vez que um pedido é colocado na linha de produção, os robôs são automaticamente "notificados".

É uma solução muito mais coerente do ponto de vista da OO, já que a responsabilidade de saber o que fazer a cada novo pedido é da linha de produção e não de quem colocou o pedido.

Apesar de quase perfeita, podemos melhorar essa solução um pouco mais no .Net  utilizando delegates.

Na realidade todos os eventos no .Net são uma implementação do padrão Observer.

Perceba no código abaixo que criamos um delegate genérico que irá chamar um método do Robô para fornecer as peças.

Apenas os robôs que “assinaram” o evento de Novo Pedido serão notificados. Não precisamos criar nenhuma lista de robôs, o próprio .Net cuida disso para nós.

No código também merece atenção o seguinte trecho:

if (Novo != null)
{
     Novo(this, e);
}

Isso garante que o evento será disparado APENAS se alguém o assinou. Se não fizermos esse controle será disparada uma excessão.

/*
 * Padrão Observer otimizado para o .Net
 * Cassio R. Eskelsen (www.bizness.com.br
 */
using System;
using System.Collections;

namespace ObserverDotNet
{
    class Program
    {
        static void Main(string[] args)
        {
            Robo wallE = new Robo("Wall-E", 1);
            Robo c3po = new Robo("C3PO", 10);
            Robo sonny = new Robo("Sonny", 20);
            Robo terminator = new Robo("Terminator 800", 1000);

            LinhaProducao linha = new LinhaProducao();
            linha.AtivarRobo(wallE);
            linha.AtivarRobo(c3po);
            linha.AtivarRobo(sonny);
            linha.AtivarRobo(terminator);
            
            Console.WriteLine("Adiciona novo pedido com todos os robôs ativos");

            linha.AdicionarPedido(new Pedido(10));

            linha.DesativarRobo(terminator);
            Console.WriteLine("Adiciona novo pedido sem um robô");
            linha.AdicionarPedido(new Pedido(5));

            Console.ReadLine();

        }
    }

    public class Pedido
    {
        public Pedido(int qtPedido)
        {
            this.QtPedido = qtPedido;
        }

        public int QtPedido { get; set; }

    }

    /// <summary>
    /// Delegate responsável por "segurar" os pedidos de notificacao de alteracao
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="U"></typeparam>
    /// <param name="sender"></param>
    /// <param name="eventArgs"></param>
    public delegate void NovoPedidoEventHandler<T, U>
       (T sender, U eventArgs);

    /// <summary>
    /// Classe responsável por transitar as informações do evento
    /// </summary>
    public class PedidoEventArgs : EventArgs
    {

        public PedidoEventArgs(int qtPedido)
        {
            this.QtPedido = qtPedido;
        }

        public int QtPedido { get; set; }
    }


    public class LinhaProducao
    {
        /// <summary>
        /// Evento
        /// </summary>
        public event NovoPedidoEventHandler<LinhaProducao, PedidoEventArgs> Novo;

        /// <summary>
        /// método responsável por invocar o evento de novo pedido
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnNovo(PedidoEventArgs e)
        {
            if (Novo != null)
            {
                Novo(this, e);
            }
        }
 

        /// <summary>
        /// Liga um robô em uma linha de produção
        /// </summary>
        /// <param name="robo"></param>
        public void AtivarRobo(Robo robo)
        {
            ///assina o evento fazendo o robô ser notificado a cada novo pedido
            /* aqui é possível chamar o evento robo.SuprirPecas pois ele tem a mesma
             * assinatura do delegate  NovoPedidoEventHandler<T, U> (T sender, U eventArgs);*/
            Novo += new NovoPedidoEventHandler<LinhaProducao, PedidoEventArgs>(robo.SuprirPecas);
            Console.WriteLine("Robô {0} plugged", robo.NomeRobo);
        }

        /// <summary>
        /// Retira um robo da lista de "observadores"
        /// </summary>
        /// <param name="robo"></param>
        public void DesativarRobo(Robo robo)
        {
            ///remove a assinatura do evento
            Novo -= new NovoPedidoEventHandler<LinhaProducao, PedidoEventArgs>(robo.SuprirPecas);
            Console.WriteLine("Robô {0} unplugged", robo.NomeRobo);
        }

        /// <summary>
        /// Dá entrada de um novo pedido na linha de produção
        /// </summary>
        /// <param name="ped"></param>
        public void AdicionarPedido(Pedido ped)
        {
            OnNovo(new PedidoEventArgs(ped.QtPedido));
        }

        public void ReceberPecasDoRobo(int qtPecas)
        {
            Console.WriteLine("Recebi {0} peças para montagem", qtPecas);
        }
    }

    public class Robo
    {

        private int _fatorPecasPorPedido = 1;

        public Robo(string nomeRobo, int fatorPecasPorPedido)
        {
            this.NomeRobo = nomeRobo;
            this._fatorPecasPorPedido = fatorPecasPorPedido;
        }

        public string NomeRobo { get; set; }

        /// <summary>
        /// para poder ser chamado pelo evento, esse método deve ter a mesma assinatura do delegate
        /// </summary>
        /// <param name="linhaDeProducao"></param>
        /// <param name="e"></param>
        public void SuprirPecas(LinhaProducao linhaDeProducao, PedidoEventArgs e)
        {
            linhaDeProducao.ReceberPecasDoRobo(e.QtPedido * this._fatorPecasPorPedido);
        }

    } 
}

3.0 ponto(s). Avaliado por 2 pessoas

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Design Pattern | .Net

Comentar


(Vai mostrar seu Gravatar)  

  Country flag

biuquote
  • Comentário
  • Pré-visualização
Loading