Design Patterns em formulários Windows complexos – Parte II

 

Um jeito diferente de fazer

Uma maneira diferente de fazer o programa usado neste artigo e aquele do qual falei no início é utilizar padrões de projeto (design patterns). Padrões de projeto são soluções simples para problemas específicos. Não exigem nenhuma característica especial da linguagem e têm o objetivo de tornar os programas mais flexíveis e reutilizáveis. O padrão que, em minha opinião, se encaixa melhor para interfaces complexas é o Chain of Responsability (cadeia de responsabilidades).  Ele tem o objetivo de evitar o acoplamento entre aquele que envia uma solicitação e aquele que a recebe. Apenas para exemplificar, no programa que citei antes, seria mais ou menos o seguinte:

  • Quando um indicador é modificado ele “envia uma mensagem” dizendo: Olá, sou o indicador X e fui alterado da maneira Y;
  • Aqueles que são afetados por esta modificação “tratam” esta mensagem de maneira apropriada.

Para usar este padrão em interfaces com usuário eu fiz algumas adaptações que somadas têm o seguinte aspecto:

 

mpdpfwcp1fig05.jpg 

 

Uma questão de nomenclatura: O padrão cita aqueles que podem manipular uma requisição como manipuladores (handlers) e aqueles que iniciam uma requisição como clientes (clients). Do ponto de vista do formulário todos podem iniciar e tratar uma requisição. Desta forma, eu escolhi chamar todos os participantes de ‘partes’ (parts). As requisições que podem ser feitas pelas partes são chamadas aqui de mensagens (messages) e a classe que controla como uma mensagem chega até todas as partes é chamada de gerenciador de mensagens (MessageManager).

 

Para ser parte participante do padrão uma classe deve implementar a interface IMessageManagerPart. Esta interface define se a parte contém subpartes e disponibiliza um método chamado ReceiveMessage que servirá para interceptar e tratar as mensagens enviadas pelo gerenciador de mensagens.

 

Um código típico para criação de uma nova parte é o seguinte:

 

public partial class DummyPart : UserControl, IMessageManagerPart

{

        private MessageManager messageManager = null;

        private IMessageManagerPartCollection subparts = null;

 

        protected DummyPart() : base()

        {

               InitializeComponent();

        }

 

        public DummyPart(MessageManager messageManager) : this()

        {

               this.messageManager = messageManager;

        }

 

        #region IMessageManagerPart Members

 

        public IMessageManagerPartCollection Subparts

        {

               get { return this.subparts; }

        }

 

        public MessageManager Manager

        {

               get { return this.messageManager; }

        }

 

        public bool ReceiveMessage(ref ImessageManagerMessage message)

        {

               return false;

        }

 

        #endregion

}

 

Uma vez definidas as partes que estarão na sua interface, será necessário começar a pensar nas mensagens que serão utilizadas por estas partes. Para criar uma mensagem será necessário utilizar a interface IMessageManagerMessage:

 

public class CloseRequested : IMessageManagerMessage

{

        #region IMessageManagerMessage Members

 

        public bool AllowMultipleHandler

        {

               get { return false; }

        }

 

        public int Id

        {

               get { return MessageManagerIdentifiers.CloseRequested; }

        }

 

        #endregion

}

 

Note a utilização da estrutura MessageManagerIdentifiers para identificar a mensagem. Esta estrutura é necessária para tornar o código mais claro, principalmente quando você for tratar a mensagem:

 

public bool ReceiveMessage(ref IMessageManagerMessage message)

{

        switch (message.Id)

        {

               case MessageManagerIdentifiers.CloseRequested:

                       {

                               this.HandleCloseRequested(message);

                               return true;

                       }

        }

 

        return false;

}

 

A propriedade AllowMultipleHandler serve para orientar o gerenciador de mensagens quanto à quantidade de vezes que a mensagem pode ser manipulada. Segundo a especificação do padrão Chain of Responsability, uma requisição é repassada pela cadeia somente até que alguém a trate. Depois disto ela deixa de ser repassada. Não obstante, algumas vezes é interessante que a mensagem seja tratada por quem tiver que ser. Daí a existência desta propriedade.

 

Neste momento já definimos as partes e as mensagens. Resta-nos agora fazer com que as mensagens sejam repassadas pela cadeia. Neste ponto você já deve ter percebido que a tal cadeia é determinada pelas partes e suas subpartes. Um exemplo de cadeia de responsabilidade que poderia ser utilizada no MS-SQL BPA pode visto abaixo:

 

MainForm

        Header

 

        Options

               Pages

               Shortcuts

        Action

               WelcomePage

               LoginPage

               ... (continua)

        Status

 

Veja que MainForm é a parte  principal  que contém quatro subpartes. A subparte Options contém duas subpartes e assim por diante. No final de tudo temos uma hierarquia de responsabilidades que será utilizada pela classe MessageManager, através do método SendMessage. Este método recebe a mensagem que deve ser repassada e a envia para a cadeia até chegar à última parte ou até que haja uma quebra no ciclo de repasse. Nós já vimos que a propriedade AllowMultipleHandler é um meio de parar o repasse. Existe mais uma forma que citarei mais adiante. Por hora gostaria de mostrar como uma parte pode disparar uma mensagem:

 

private void DummyPart_Click(object sender, EventArgs e)

{

        IMessageManagerMessage closeRequested = new CloseRequested();

        this.Manager.SendMessage(ref closeRequested);

}

 

Algo que você pode estar pensando é porque a mensagem tem que ser uma classe. Além disto, porque ela tem que ser passada por referência. Algo que acontece muito comigo é usar as mensagens e seus estados nos tratamentos. Além disto, você pode projetar sua estrutura de mensagens para fazer com que seja possível à mensagem “ganhar experiência” durante sua viagem pela cadeia de responsabilidades. Isto significaria ter propriedades de escrita na mensagem e é algo que tem tantas implicações que não será abordado neste artigo.

 

Eventos do MessageManager

A classe MessageManager possui três eventos:

 

BeforeProcessMessage: Pode ser utilizado pelas partes para interceptar uma mensagem antes que ela comece a ser repassada pela cadeia. Há uma propriedade ‘Cancel’ no argumento do evento que quando configurada para true, sinaliza ao MessageManager que a mensagem não deve ser repassada.

 

AfterProcessMessage: Ocorre depois que a mensagem for repassada pela cadeia. Há uma propriedade ‘Continue’ no argumento do evento que quando configurada para false, sinaliza ao MessageManager que nenhum manipulador deste evento deve mais ser executado.

 

ProcessMessageUnhandledException: Ocorre toda vez que, durante um ciclo de repasse, ocorrer uma exceção não tratada. Idealmente isto jamais deveria ocorrer. As partes deveriam tratar suas próprias exceções e deixar exceções não tratadas ocorrerem durante o tratamento de uma mensagem deve ser encarado como um mau sinal.

 

Vantagens e desvantagens

Vantagens:

·         Não existe nenhum acoplamento entre as diversas partes que compõe o formulário;

·         Adicionar uma nova parte, mesmo que seja condicionalmente, faz a estrutura crescer organizadamente (devido à hierarquia entre as partes) e não simplesmente inchar;

·         O código gerado favorece a utilização de herança;

·         O programa gerado é implicitamente mais flexível, pois alterar responsabilidades exige uma mera alteração na cadeia de responsabilidades. E isto pode ser feito em qualquer tempo;

·         O padrão desestimula a utilização de interfaces baseadas em camadas, o que torna a manutenção do designer bem mais simples.

 

Desvantagens:

·         Caso você utilize os eventos do gerenciador e/ou mais de um manipulador para a mesma mensagem, a depuração pode ficar um pouco mais difícil;

·         O tratamento de uma mensagem não é garantido;

·         Sua interface com o usuário pode ocupar mais memória, dependendo da quantidade de partes e sub-partes existentes;

·         Sua interface com o usuário pode ser um pouco mais lenta, dependendo da quantidade de partes e subpartes existentes. Não obstante, levando-se em conta estamos falando de códigos para camada de apresentação, isto é na maioria das vezes imperceptível.

 

Conclusões

Você deve ter notado pela comparação dos dois códigos fonte que utilizar este (ou qualquer outro) padrão tornou a solução um pouco mais trabalhosa do que sair ‘apontando e clicando’ no formulário. Esta percepção é certa. Realmente dá um pouco mais de trabalho.  O que eu penso sempre neste tipo de caso é o que eu ganho e o que eu perco adotando uma ou outra solução.  Minha percepção sobre padrões de maneira geral é que o esforço adicional vale a pena.

 

Para finalizar e ainda sobre o mesmo assunto, outro dia estava vendo um software de frente de caixa que suportava não sei quantas impressoras fiscais. O problema nele é que a cada nova impressora implementada outras tantas ficam com defeito (é tudo em uma classe só). Veja, a simples utilização de um padrão de criação simplificaria algo que atualmente é feito com luva e bisturi. Mas isto é história para outro artigo.

Leia também