[Delphi] Design Patterns GoF – Decorator

Boa noite, caros leitores!
Para sair um pouco da rotina, gostaria de convidá-los para aprender a trabalhar com decoração, haha!
Brincadeiras à parte, embora o artigo de hoje realmente não deixe de ser sobre decoração, apresentarei o padrão de projeto Decorator. Assim como o Bridge, um dos maiores propósitos deste padrão é reduzir as heranças de uma hierarquia de classes. Vamos decorar?

Introdução

Como já sabemos, um dos pilares da Programação Orientada a Objetos é a herança, que nos fornece um mecanismo para eliminação de código duplicado através do compartilhamento vertical de métodos e campos. Porém, sabe-se também que o excesso de heranças em uma hierarquia pode promover o aumento da complexidade e a dificuldade de manutenção do código. Uma pequena alteração em uma parte da hierarquia pode gerar um impacto em todas as classes derivadas, trazendo, talvez, comportamentos inesperados.

O Design Pattern Decorator apresenta uma alternativa às heranças. Na verdade, a palavra “decorator” nos remete a um sentido de decoração que, em termos gerais, é o que realmente ocorre ao empregarmos este padrão de projeto.

O objetivo do Decorator, como veremos logo a seguir, é adicionar funcionalidades extras a um objeto em tempo de execução, de forma que ele possa ter novos comportamentos que originalmente não fazem parte de sua estrutura.

Na internet, é comum encontrar dois exemplos clássicos deste padrão: a janela e a forma geométrica. No primeiro, adiciona-se acessórios na janela, como cortinas, persianas ou grades. Já no outro, enfeita-se uma forma geométrica, desenhando bordas e preenchimentos. São exemplos que, apesar de ilustrativos, expressam o propósito do Decorator, porém, não refletem uma ideia de aplicação real.

Aqui no blog, apresentarei alguns cenários mais voltados à realidade.

Exemplo de codificação do Decorator

Quando estudei os conceitos do Decorator, a princípio imaginei um contexto de um sistema de envio de mensagens. Inicialmente, teríamos um objeto de uma classe de mensagens e que, quando necessário, seria “decorado” por funcionalidades complementares, como assinatura, encriptação ou formatação HTML. Em tempo de execução, criaríamos o decorador de mensagens solicitado, como para a assinatura, e faríamos um vínculo com o objeto de mensagem. A partir deste momento no fluxo, o objeto ganharia, então, uma propriedade de assinatura.

Na teoria, o padrão Decorator apresenta 4 elementos:

  • Component: Interface comum que será implementada tanto pelas classes que poderão ser decoradas quanto pelas classes decoradoras;
  • Concrete Component: Classes que implementam Component e que apenas poderão receber “decorações”, ou melhor, receber funcionalidades extras;
  • Decorator: Classe abstrata que implementa Component e atua como classe base de todas as decorações possíveis;
  • Concrete Decorator: Classes que herdam de Decorator e exercem o papel de “decoradoras”.

No exemplo sobre o sistema de envio de mensagens, portanto, teríamos as seguintes interfaces e classes:

  • IMensagem: elemento Component;
  • TMensagem: elemento Concrete Component (implementa IMensagem);
  • TDecoradorMensagem: elemento Decorator (implementa IMensagem);
  • TAssinatura, TEncriptacao e TFormatacaoHTML (herdam de TDecoradorMensagem): Concrete Decorators.

Embora o exemplo sobre o sistema de mensagens seja bem compreensível, pensei que um cenário diferente para a nossa aplicação prática poderia agregar ainda mais. Sendo assim, implementaremos uma classe que exibe detalhes de exceções que ocorrem no sistema. A princípio, a classe apenas irá apenas exibir a mensagem de exceção, e iremos decorá-la para exibir informações adicionais.

Interface Component

O primeiro passo é criar a Interface Component. Ressalto aqui que, na maioria dos artigos, este sempre será o primeiro passo, já que devemos partir da abstração.

O método ObterDadosExcecao será responsável por devolver uma string com os detalhes da exceção solicitados.

Classe Concrete Component

O nosso Concrete Component, que implementa a Interface acima, terá a seguinte implementação:

Veja que a classe TLogExcecao, em seu estado primitivo, apenas retorna a mensagem de exceção, porém, na maioria das vezes, só isso não é o suficiente. Precisamos de mais detalhes para conseguirmos rastrear o erro. Partiremos, então, para a parte de “decoração”.

Classe Decorator

O próximo passo é criar o elemento Decorator, que será uma classe abstrata com uma característica muito importante: possui um campo que mantém uma referência ao objeto que será decorado:

Explico o motivo da referência: antes de adicionarmos as funcionalidades extras, devemos executar primeiro os comportamentos do objeto original. Em outras palavras, o método ObterDadosExcecao será executado para o objeto inicial e só depois para as decorações. Continue acompanhando o artigo.

Classes Concrete Decorators

O último passo é finalmente criar as decorações. A primeira delas, que irá adicionar a informação de data e hora da exceção, terá a seguinte codificação:

Observe que a variável result, em primeiro lugar, é preenchida com o resultado do método da classe ancestral que, por sua vez, executa o método do ConcreteComponent.

O decorador do nome do usuário terá uma codificação semelhante:

Por fim, para a decoração da versão do Windows, acessaremos o registro do sistema operacional para obter os números da versão:

Perfeito. Agora é só colocar tudo isso em prática. Já digo de passagem: é bem simples!

Em ação!

Já que se trata de um gravador de log de exceções, achei oportuno utilizá-lo no evento OnException do componente TApplicationEvents, pois, neste ponto, temos acesso o objeto de exceção que vem por parâmetro (E). Neste evento, criamos uma instância da classe de gravação de log e, em seguida, aplicamos qualquer decoração que for necessária:

Legal, não é? Mas podemos ainda ir além. No exemplo acima, há três condições IF que vinculam a decoração quando são atendidas, porém, é importante compreender que o objeto não se limita a somente um tipo de decoração. Podemos aplicar os 3 juntos!

Neste caso, a mensagem de decoração, a data e hora, o nome do usuário e a versão do Windows serão exibidos de uma vez só. Vale explicar que, para que isso seja possível, uma decoração deve “decorar a outra”, como exemplificado abaixo:

No more inheritances! 🙂

Conclusão

Eu diria que, neste contexto do Decorator, acontece, sim, uma espécie de herança, mas de forma horizontal.

Uma vantagem do Decorator que não posso deixar de citar é a possibilidade de decorar qualquer outro objeto do sistema, claro, desde que ele seja do tipo de uma classe que implementa a Interface Component. Por exemplo, podemos aplicar essas mesmas decorações para uma classe de auditoria ou até mesmo em uma classe de mensagem de e-mail, como mencionado no início do artigo. Herança? Não precisa!

Bom, pessoal, antes de finalizar, gostaria de mencionar um fato. Enquanto eu procurava um contexto para apresentar o exemplo prático do artigo, a minha esposa (também conhecida como “Soberana do JMeter”) sugeriu um sistema de pagamentos que se encaixaria perfeitamente no artigo. Neste cenário, teríamos um tipo de pagamento que poderia ser “decorado” de três formas: por boleto, depósito ou cartão de crédito. Para cada forma, o programa criaria, em tempo de execução, os campos para digitação, como código de barras, agência/banco e número do cartão, respectivamente. Infelizmente não pude utilizá-lo em função da quantidade de número de linhas de código, pois deixaria o artigo muito extenso.

Assim como todos os outros artigos, disponibilizei um projeto de exemplo no link abaixo. Faça o download, execute-o no Delphi e confira a decoração do objeto conforme a seleção dos CheckBoxes na tela. Para testar melhor o exemplo (sem que o Delphi interrompa a execução a cada exceção), desmarque a opção “Stop on Delphi Exceptions” nas opções da IDE.

 

Estou à disposição para esclarecer qualquer dúvida, leitores!
Um grande abraço a todos e até o próximo padrão de projeto!


 

André Celestino