Siga-me

sábado, 10 de março de 2012

Eventos e Delegates

EVENTOS E DELEGATES

Olá.

Pensado eu ser este um assunto muito discutido nos fóruns de desenvolvimento de software, resolvi demonstrar um exemplo simples e prático aqui no blog.

Observação: quem não conhece o termo DDD - Domain Driven Design, aconselho o livro (http://books.google.com.br/books?id=7dlaMs0SECsC&printsec=frontcover&redir_esc=y#v=onepage&q&f=false) do Eric Evans.

Imagine a seguinte situação: sua aplicação possui uma camada de apresentação (Windows Forms ou Asp.Net) que utiliza classes que estão localizadas em uma Class Library que faz parte da camada de Domínio (onde residem as entidades, repositórios, fábricas, etc). Uma dessa classes possui propriedades que, dada a ocorrência de alguns comportamentos, são alteradas e sua aplicação precisa ser notificada de tais alterações para exibir os resultados ao usuário. Não seria uma prática ideal adicionar o namespace System.Windows.Forms na referida classe para utilizar a classe MessageBox, já que não deve-se utilizar de tecnologias na camada de Domínio. Injetar referências para métodos que exibe telas da apresentação através de delegates também não é lá das melhores práticas.


Então, qual seria a solução mais viável? Eventos e Delegates!

O próprio padrão de arquitetura da Dot Net utiliza esses meios. Quando adicionamos um botão em nossa tela ou página, por exemplo, o objeto nos oferece vários eventos para utilizarmos ou não (Click, MouseUp, etc...). Podemos criar nosso próprios eventos para que possamos notificar a aplicação sobre qualquer alteração nas propriedades e comportamento do nosso objeto.
A tradução do conceito de eventos contido no site do msdn é a seguinte:
"Eventos habilitam uma classe ou objeto para notificar outras classes ou objetos quando ocorrer algo de interesse. A classe que envia (ou gera) o evento é chamado de publisher e as classes que recebem (ou lidam com) o evento são chamados assinantes."
Já o delegate é a referência (um ponteiro) para um método específico (assim como existem referências para variáveis) desde que tal método tenha a mesma assinatura do delegate.

Esses conceitos ficam melhor explicados na prática, lembrando que o exemplo é apenas para fins didáticos:

- Abra o Visual Studio 2010 e crie uma aplicação WindowsForms (Menu File > New > Project - Aba Windows > WindowsFormsApplication) e dê o nome de ExemploEventos;

- No formulário padrão que o Visual Studio cria, insira dois buttons e um listbox;

- No Button1, altere a propriedade Text para Ligar;

- No Button2, altere a propriedade Text para Desligar;

- Deixe a aparência do seu formulário como na figura abaixo:


- Na SolutionExplorer, clique com o botão direito do mouse, na opção Add > Class. Dê o nome de Carro.
- Nossa classe carro irá conter as seguintes propriedades dinâmicas:

        // Indica o total de litros existentes no tanque do carro        int LitrosNoTanque { get; set; }        // Indica se o carro está ligado ou desligado        bool StatusLigado { get; set; }        // Indica se o carro está em movimento ou parado        bool StatusMovimento { get; set; }

- A ideia é ter os seguintes comportamentos: ligar, desligar, partir e parar; e para cada alteração de comportamento do carro criar um evento notificador;

-Todo evento tem um TIPO, que geralmente é um delegate para um método. Então, temos que pensar na forma como iremos notificar nosso usuário. Neste exemplo pensei apenas em um método simples de notificação para exibir as mensagens específicas de cada status informado e um outro para recuperar a quantidade atual de litros no tanque. Por conversão, deve-se colocar a palavra EventHandle no final do nome de cada delegate que será um tipo para um evento. Então, os delegates da classe serão estes:

        // Delegate para um método simples de exibição de status
        public delegate void InformacoesCarroEventHandle();
        // Delegate para um método que utiliza a propriedade LitrosNoTanque,         // através da variável inteira passada como parâmetro no método 
        // para informar tal status ao usuário
        public delegate void InformacaoTanqueEventHandle(int litrosNoTanque);

- Pronto, nossos delegates foram declarados.

-Teremos um evento para cada notificação desejada. A sintaxe de declaração é a seguinte:
     

        // Evento para notificar que o carro ligou
        public event InformacoesCarroEventHandle CarroLigado;        // Evento para notificar que o carro desligou        public event InformacoesCarroEventHandle CarroDesligado;        // Evento para notificar que o carro está em movimento        public event InformacoesCarroEventHandle CarroEmMovimento;        // Evento para notificar que o carro parou        public event InformacoesCarroEventHandle CarroParado;        // Evento para notificar que o carro está sem gasolina        public event InformacoesCarroEventHandle TanqueVazio;        // Evento para notificar a quantidade de litros no tanque        public event InformacaoTanqueEventHandle QuantidadeLitrosNoTanque;

- Nossa classe Carro terá um construtor que informará a quantidade de gasolina inserida no tanque do carro:

        // Contrutor do objeto. Inicializa a propriedade LitrosNoTanque
        public Carro(int litrosNoTanque)        {
            this.LitrosNoTanque = litrosNoTanque;
        }

- O primeiro método é de ligar. Observe que, se o carro estiver ligado ou sem gasolina tal status é notificado pelo seu evento específico e, assim que o carro é ligado, o evento de notificação é chamado:


        // Efetua verificações e liga o carro        public void Ligar()        {
            if (this.StatusLigado)
            {
                this.CarroLigado();
 
                return;
            }
 
            if (this.LitrosNoTanque.Equals(0))
            {
                this.TanqueVazio();
 
                return;
            }
 
            this.StatusLigado = true;
 
            this.CarroLigado();
        }


- Da mesma forma acontece com o método desligar:

        // Efetua validações e desliga o carro        public void Desligar()        {
            if (!this.StatusLigado)
            {
                this.CarroDesligado();
 
                return;
            }
 
            this.StatusLigado = false;
 
            this.CarroDesligado();            
        }

- O processo de movimentar o carro possui mais detalhes. Após dar a partida no carro inicia o processo de consumo de combustível que também é notificado com seu evento:

        // Efetua validações e dar a partida no carro. Da a partira no carro        // e inicia o processo de consumo de gasolina
        public void Partir()        {
            if (!this.StatusLigado)
            {
                this.CarroDesligado();
 
                return;
            }
 
            if (this.LitrosNoTanque.Equals(0))
            {
                this.TanqueVazio();
 
                return;
            }
            
 
            this.StatusMovimento = true;
 
            this.CarroEmMovimento();
 
            while (this.LitrosNoTanque > 0)
            {
                System.Threading.Thread.Sleep(1000);
                
                this.LitrosNoTanque--;
 
                this.QuantidadeLitrosNoTanque(LitrosNoTanque);
 
                if (LitrosNoTanque.Equals(0) || !this.StatusLigado)
                {
                    this.TanqueVazio();
 
                    this.Parar();
 
                    this.Desligar();
 
                    break;
                }
            }
        }


- E por fim, o método de parar:

        // Efetua validações e pára o carro        public void Parar()        {
            if (!this.StatusLigado)
            {
                this.CarroDesligado();
 
                return;
            }
 
            if (!this.StatusMovimento)
            {
                this.CarroParado();
 
                return;
            }
 
            this.StatusMovimento = false;
 
            this.CarroParado();
        }
    }

-Nossa classe Carro está pronta. Precisamos agora utilizá-la em nossa windows application. Volte para a janela e pressione F7 para exibir seu CodeBehind.

- No topo da classe declare uma propriedade dinâmica da classe Carro. Logo abaixo, no construtor da janela, instancie o nosso carro e crie os event handles de notificações:

        public Carro Palio { get; set; }
        
        public Form1()
        {
            InitializeComponent();           
 
            this.Palio = new Carro(20); 
            
            Palio.CarroLigado += new Carro.InformacoesCarroEventHandle(Palio_CarroLigadoEventHandle);
 
            Palio.CarroDesligado += new Carro.InformacoesCarroEventHandle(Palio_CarroDesligadoEventHandle);
 
            Palio.CarroEmMovimento += new Carro.InformacoesCarroEventHandle(Palio_CarroEmMovimentoEventHandle);
 
            Palio.CarroParado += new Carro.InformacoesCarroEventHandle(Palio_CarroParadoEventHandle);
 
            Palio.TanqueVazio += new Carro.InformacoesCarroEventHandle(Palio_TanqueVazioEventHandle);
 
            Palio.QuantidadeLitrosNoTanque += new Carro.InformacaoTanqueEventHandle(Palio_LitrosNoTanqueEventHandle);
        }
 
        void Palio_LitrosNoTanqueEventHandle(int litrosNoTanque)
        {
            listBox1.Items.Add(string.Format("Capacidade atual do Tanque {0}", litrosNoTanque));
        }
 
        void Palio_CarroParadoEventHandle()
        {
            listBox1.Items.Add("O carro parou!");
        }
 
        void Palio_TanqueVazioEventHandle()
        {
            listBox1.Items.Add("O carro está sem gasolina!");
        }
 
        void Palio_CarroEmMovimentoEventHandle()
        {
            listBox1.Items.Add("O carro está andando!");
        }
 
        void Palio_CarroDesligadoEventHandle()
        {
            listBox1.Items.Add("O carro desligou!");
        }
 
        void Palio_CarroLigadoEventHandle()
        {
            listBox1.Items.Add("O Carro ligou!");
        }


- Volte para a janela e dê um duplo clique no button1 (isso criará um event handle para o evento click :-) e insira o trecho de código abaixo:

        private void button1_Click(object sender, EventArgs e)
        {
            this.Palio.Ligar();
 
            Thread t = new Thread(new ThreadStart(Palio.Partir));
 
            CheckForIllegalCrossThreadCalls = false;
 
            t.Start();
        }


- Explicando o código do button1:
  * A primeira linha chama o método ligar do objeto pálio.
  * O método partir, que é chamado logo em seguida é um método que ocorre em "paralelo" com o método ligar para que a janela consiga exibir as mensagens a cada processamento do consumo de combustível. então, foi criada uma Thread além da Thread principal para processar o método de forma assíncrona.
  * A terceira linha desabilita a checagem de chamadas cruzadas por threads.
  * A quarta inicia a Thread efetivando o procedimento partir.

- Volte para a janela e dê um duplo clique no button2 e insira o código para parar o carro. Como o método partir está sendo executado em segundo plano, este método pode ser chamado enquanto o carro estiver consumindo combustível:

        private void button2_Click(object sender, EventArgs e)
        {
            this.Palio.Desligar();
        }


- Salve e construa sua solução. Pressione F5 para iniciar a plicação, clique no botão Ligar e veja as notificações, como na janela abaixo:


Então é isso pessoal. Espero que ajude!!!

Referências:
http://msdn.microsoft.com/pt-br/library/awbftdfh(v=vs.90).aspx
http://www.macoratti.net/11/05/c_dlg1.htm
http://msdn.microsoft.com/en-us/library/orm-9780596516109-03-09.aspx

Um comentário:

Anônimo disse...

Muito bom artigo, sempre procurei algo na net e não achei como explicado aqui. Parabéns!