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:
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.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.
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.