24 de março de 2010

Design Patterns com Delphi : Comando - parte I

É bastante comum no desenvolvimento de software a situação em que um mesmo tipo de comando pode ser ativado por diferentes meios. Por exemplo, no ERP Omega da ABC71 um mesmo processo ou relatório pode ser executado diretamente pelo usuário ou essa execução pode ser agendada para executar mais tarde, num horário mais apropriado.

Esse tipo de situação é abordada pelo Design Pattern comportamental Comando. Nesse Pattern, uma requisição é encapsulada como uma objeto, facilitando a configuração de chamadas a essa requisição em diversas circunstâncias. Tendo comandos como um objeto, é possível criar filas de execução de comandos (como num Wizard), implementar o recurso de "desfazer" em um programa (percorrendo a lista de comandos executados e chamando um método "undo" que deve existir em cada objeto) ou até mesmo permitir a gravação de macros em seu programa (na prática, uma lista de comandos criada e mantida diretamente pelo usuário).

No ERP da ABC71, temos um conceito de "tabelas integradoras" onde softwares de terceiros podem armazenar informações que devem ser importadas por nós. O processo de importação lê os registros gravados nessas tabelas e aplica a eles nossas regras de negócio de modo que os dados são corretamente incorporados ao ERP. Cada tipo de importação pode ser encarado como um comando separado e o usuário pode escolher numa tela quais os comandos e respectivos parâmetros. Os comandos poderiam também ser acessados individualmente, fora da tela. O diagrama abaixo mostra uma visão simplificada da solução para esse cenário usando o padrão Command:
Diagrama UML para o padrão Command
De acordo com o objetivo pretendido, pode haver pequenas variações nas relações entre as classes mas o que está representado no diagrama é uma boa base para o Command. No quadro abaixo aparece a nomenclatura usual para as classes participantes da solução:
Comand é uma interface que estabelece as operações que estarão disponíveis num comando. No exemplo acima, a interface TWComandoBase detem esse papel ao introduzir as operações Execute e Undo. Prevendo que nem todo comando poderá ser desfeito, há também a função IsUndoable, permitindo que cada comando estabeleça se poderá ou não ser desfeito.

O Receiver é qualquer classe que tenha o conhecimento de como executar a ação associada a um comando. Trata-se, portanto, do objeto que executa de fato a ação requisitada. No diagrama, esse é o papel da classe TWImportableObj e suas heranças.

Um ConcreteCommand provê a ligação entre o objeto Receiver e a ação esperada para o comando. Isto implica que, ao criarmos a instância do ConcreteCommand, devemos vincular um Receiver a ela, promovendo o desacoplamento entre este Receiver e quem irá invocar o comando (o Invoker). Quem invoca o comando apenas espera que ele seja executado, sem se preocupar com os detalhes da implementação; quando se requisita ao comando que execute a ação, o ConcreteCommand redireciona a requisição de forma transparente para o Receiver. No diagrama acima, aparecem duas classes que agem como ConcreteComand : TWComandoCarga e TWComandoExecProc.

A classe Invoker é quem solicita a um comando que execute a ação associada. No nosso exemplo, a classe TWComandos executa esse papel de uma forma uma pouco mais elaborada. Aqui, ele aceita uma lista de comandos - quaisquer objetos que implementem o TWComandoBase - e os executa sequencialmente sem precisar ter conhecimento sobre como cada comando realiza sua operação. Ou seja, o Invoker está desacoplado do Receiver.

A responsabilidade do Client é criar corretamente todos os ConcreteComand necessários e ajustar o Receiver apropriado para cada um. Os objetos assim criados são adicionados à lista de comandos representada pelo Invoker e este é acionado para executar cada comando. No exemplo, o papel de Client é reservado para a classe TWFormImportaRegs, que neste caso é uma tela que oferece ao usuário a oportunidade de selecionar os comandos que ele deseja executar bem como suas respectivas configurações.
Note que é possível criar instâncias isoladas das heranças de TWImportableObj e requisitar a importação separadamente a partir delas. A vantagem de fazer isso usando o Invoker é que este flexibiliza o uso dos comandos, possibilitando, por exemplo, a execução de importações (e quaisquer outros comandos que implementem a interface TWComandoBase) em agendamentos, em qualquer ordem.

Volto no próximo post para mostrar um mapeamento das classes envolvidas usando o Delphi.

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.