Olá, leitores! Já estamos no 14º artigo sobre Design Patterns!
Dentre todos os padrões de projetos já abordados até o momento, o Command, que será visto neste artigo, foi um dos mais custosos para compreender. Não pelo propósito, mas pela arquitetura de classes que são exigidas e também pela empregabilidade em um ambiente real. Procurei ser o mais objetivo possÃvel para evitar que o artigo ficasse muito extenso. Check it out!
Â
Um fato bem interessante que observei ao estudar os padrões de projeto comportamentais é a possibilidade de “personalizar” as ações dos objetos. No artigo passado, sobre o Chain of Responsibility, por exemplo, nos é concedida a liberdade de compor uma cadeia de responsabilidades por qualquer objeto e em qualquer ordem. Neste artigo você também irá notar essa mesma natureza de customização de ações.
O objeto principal do Command, em poucas palavras, é encapsular requisições como objetos, possibilitando a criação de filas de comandos a serem executados sob determinada ordem. Logo, podemos “empilhar” vários comandos e executá-los de uma só vez somente quando necessário.
Â
Não entendi…
Sem problemas. Para introduzir o conceito do Command, considere um wizard de instalação de um programa, como o da imagem abaixo:
Para muitos, esses instaladores são popularmente conhecidos como “Next, Next, Next, Finish”. 😛
Pois bem, através destes wizards, sabemos que é possÃvel personalizar a instalação do programa, selecionando as permissões, pasta de cópia dos arquivos, recursos que serão instalados e criação de atalhos, certo? Porém, lembre-se que a instalação, de fato, só ocorre quando o usuário pressiona o botão “Finalizar” (ou Finish). Isso significa que o wizard “empilha” os comandos (que são as opções de instalação) e somente os executa sob a ação do botão “Finalizar”.
Bom, de forma bem simples, estamos basicamente falando de uma aplicação tradicional do padrão de projeto Command! Esse procedimento poderia ser traduzido no código abaixo:
Instalador.Add(ComandoConfigurarPermissoes);
Instalador.Add(ComandoCopiarArquivos);
Instalador.Add(ComandoCriarAtalhos);
Instalador.InstalarPrograma;
Â
Utilizar o Command é o mesmo que dizer: “Tenho várias operações preparadas, mas elas só serão executados sob meu comando!” . Interessante, não?
Embora o conceito esteja um pouco mais claro, ainda não paramos por aqui. A teoria do padrão de projeto estabelece que os comandos, por si só, não executam nada. Cada comando deve estar associado a um objeto que, por fim, possui o conhecimento de como executar a operação. Portanto, existe mais um elemento nesse contexto, comentado logo a seguir.
Considere a seguinte analogia: trabalhamos em um setor de processamento de dados e devemos realizar todo o trâmite de emissão de nota fiscal quando um pedido de venda é recebido. Este pode ser um bom cenário para a aplicação do Command:
Pedido := TPedido.Create;
Processamento.Add(ComandoLancarMovimentoCaixa(Pedido));
Processamento.Add(ComandoEmitirNotaFiscal(Pedido));
Processamento.Add(ComandoEnviarParaSetorContabil(Pedido));
Processamento.ExecutarTarefas;
Cada comando recebe a instância do objeto que utilizaremos na execução de nossas tarefas. Este objeto poderia, por exemplo, ser substituÃdo por uma ordem de serviço, mas os comandos continuariam os mesmos. Do mesmo modo, é possÃvel também adicionar novos comandos, como exportar a nota fiscal para um formato especÃfico. Por esse motivo que mencionei no primeiro parágrafo que padrões dessa famÃlia nos permitem configurar ações!
Â
Por que os comandos precisam estar atrelados a um objeto?
Os comandos atuam como “conexões” entre o cliente e as ações desejadas do objeto. Essa definição nos leva a um alto Ãndice de desacoplamento entre classes, já que os comandos não conhecem os detalhes de implementação das ações que serão executadas. Os comandos, então, apenas recebem uma instância do objeto que possui as implementações e chamam um de seus métodos.
Na analogia acima, o “Pedido”, que é a classe que contém as implementações, é chamada de Receiver. Os comandos (lançar movimento do caixa, emitir nota fiscal e enviar para a contabilidade) são classes que implementam uma Interface chamada Command e são definidas como Concrete Command. A classe “Processamento”, que armazena a lista de comandos, recebe o nome de Invoker. Só a tÃtulo de curiosidade, quando estudei o padrão, lembrei do Invoker do Dota 2:
Â
A próximo parte do artigo é apresentar uma codificação prática do Command, no entanto, o exemplo não será um wizard de instalação e nem uma aplicação para processamento de pedidos. Codificaremos um sistema de extração de informações do computador, no qual pode ser útil para algumas funcionalidades. Por exemplo, imagine que uma equipe de suporte de uma empresa de software esteja recebendo vários chamados referentes à  falhas em algumas rotinas do sistema. Para averiguar a causa, o analista de suporte poderá executar a nossa aplicação e extrair a lista de processos em execução, programas instalados e as variáveis de ambiente do computador do cliente. Chique, hein?
Essa aplicação terá a seguinte interface visual:
Â
Â
O botão “Executar Comandos” irá empilhar a lista de comandos para executá-los de uma só vez, extraindo todas as informações e carregando-as nos componentes TMemo.
O primeiro passo é criar a classe Receiver, responsável por compreender a implementação de todas essas extrações. O código a seguir é um pouco extenso por englobar as três operações, porém, não deixa de servir como uma fonte de pesquisa de como extrair essas informações pelo Delphi. 🙂
type
{ Receiver }
TReceiver = class
public
procedure ExtrairProcessos;
procedure ExtrairProgramas;
procedure ExtrairVariaveisAmbiente;
end;
implementation
uses
Dialogs, Messages, Windows, TlHelp32, psAPI, SysUtils, Registry, Classes;
{ TReceiver }
procedure TReceiver.ExtrairProcessos;
var
Handle: THandle;
ProcessEntry: TProcessEntry32;
ListaProcessos: TStringList;
begin
// Método responsável por extrair a lista de processos em execução
Handle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
ListaProcessos := TStringList.Create;
try
ProcessEntry.dwSize := SizeOf(TProcessEntry32);
Process32Next(Handle, ProcessEntry);
// Percorre os processos que estão em execução,
// adicionando-os na variável ListaProcessos
repeat
ListaProcessos.Add(ProcessEntry.szExeFile)
until not Process32Next(Handle, ProcessEntry);
// Grava a lista de processos no arquivo "Processos.txt"
ListaProcessos.SaveToFile(GetCurrentDir + '\Processos.txt');
finally
CloseHandle(Handle);
end;
end;
procedure TReceiver.ExtrairProgramas;
const
CHAVE_REGISTRO = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\';
var
Registro: TRegistry;
Contador: integer;
ListaChaves: TStringList;
ListaProgramas: TStringList;
begin
// Método responsável por extrair a lista de programas instalados
// Obs: a aplicação deve ser executada como Administrador
Registro := TRegistry.Create;
ListaChaves := TStringList.Create;
ListaProgramas := TStringList.Create;
try
// Obtém a lista de chaves existentes no caminho do registro
// que armazena as informações de programas instalados
Registro.RootKey := HKEY_LOCAL_MACHINE;
Registro.OpenKey(CHAVE_REGISTRO, False);
Registro.GetKeyNames(ListaChaves);
Registro.CloseKey;
// Percorre a lista de chaves para acessar o valor do atributo "DisplayName"
for Contador := 0 to Pred(ListaChaves.Count) do
begin
Registro.RootKey := HKEY_LOCAL_MACHINE;
Registro.OpenKey(CHAVE_REGISTRO + ListaChaves[Contador], False);
if Registro.ReadString('DisplayName') <> EmptyStr then
// Adiciona o nome do programa na variável ListaProgramas
ListaProgramas.Add(Registro.ReadString('DisplayName'));
Registro.CloseKey;
end;
// Grava a lista de processos no arquivo "Programas.txt"
ListaProgramas.SaveToFile(GetCurrentDir + '\Programas.txt');
finally
// Libera os objetos da memória
FreeAndNil(ListaProgramas);
FreeAndNil(ListaChaves);
FreeAndNil(Registro);
end;
end;
procedure TReceiver.ExtrairVariaveisAmbiente;
var
Variavel: PChar;
ListaVariaveis: TStringList;
begin
// Método responsável por extrair a lista das variáveis de ambiente
ListaVariaveis := TStringList.Create;
try
// Obtém a lista das variáveis de ambiente
Variavel := GetEnvironmentStrings;
while Variavel^ <> #0 do
begin
ListaVariaveis.Add(StrPas(Variavel));
ListaVariaveis.Add(EmptyStr);
Inc(Variavel, lStrLen(Variavel) + 1);
end;
FreeEnvironmentStrings(Variavel);
// Grava a lista de variáveis no arquivo "Variaveis.txt"
ListaVariaveis.SaveToFile(GetCurrentDir + '\Variaveis.txt');
finally
// Libera o objeto da memória
FreeAndNil(ListaVariaveis);
end;
end;
Â
No segundo passo, codificaremos a Interface Command, que representa uma abstração de todos os comandos que serão implementados. Vale destacar que essa Interface declara apenas um método – tradicionalmente chamado de Execute – no qual irá acessar os métodos do Receiver.
type
{ Command }
ICommand = interface
['{68D92B0C-3342-4BB2-9C69-5AA46069A467}']
procedure Execute;
end;
Â
Em seguida, precisamos criar os comandos concretos, ou melhor, as classes Concrete Command, cada qual se responsabilizado por uma ação especÃfica do Receiver:
type
{ Concrete Command }
TProcessos = class(TInterfacedObject, ICommand)
private
// Variável para armazenar a referência do Receiver
FReceiver: TReceiver;
public
constructor Create(Receiver: TReceiver);
// Assinatura da Interface - Método de execução da operação
procedure Execute;
end;
{ Concrete Command }
TProgramas = class(TInterfacedObject, ICommand)
private
// Variável para armazenar a referência do Receiver
FReceiver: TReceiver;
public
constructor Create(Receiver: TReceiver);
// Assinatura da Interface - Método de execução da operação
procedure Execute;
end;
{ Concrete Command }
TVariaveisAmbiente = class(TInterfacedObject, ICommand)
private
// Variável para armazenar a referência do Receiver
FReceiver: TReceiver;
public
constructor Create(Receiver: TReceiver);
// Assinatura da Interface - Método de execução da operação
procedure Execute;
end;
implementation
{ TProcessos }
constructor TProcessos.Create(Receiver: TReceiver);
begin
FReceiver := Receiver;
end;
procedure TProcessos.Execute;
begin
FReceiver.ExtrairProcessos;
end;
{ TProgramas }
constructor TProgramas.Create(Receiver: TReceiver);
begin
FReceiver := Receiver;
end;
procedure TProgramas.Execute;
begin
FReceiver.ExtrairProgramas;
end;
{ TRecursos }
constructor TVariaveisAmbiente.Create(Receiver: TReceiver);
begin
FReceiver := Receiver;
end;
procedure TVariaveisAmbiente.Execute;
begin
FReceiver.ExtrairVariaveisAmbiente;
end;
Â
O último passo é elaborar a classe Invoker do Dota 2, que possui uma codificação bem simples:
type
{ Invoker }
TInvoker = class
private
// Lista para armzenar os comandos
Comandos: TInterfaceList;
public
constructor Create;
procedure Add(Comando: ICommand);
procedure ExtrairInformacoes;
end;
implementation
{ TInvoker }
procedure TInvoker.Add(Comando: ICommand);
begin
// Adiciona o comando na lista
Comandos.Add(Comando);
end;
constructor TInvoker.Create;
begin
// Cria a lista que armazenará os comandos
Comandos := TInterfaceList.Create;
end;
procedure TInvoker.ExtrairInformacoes;
var
Contador: integer;
begin
// Percorre a lista de comandos armazenados,
// executando a operação de cada um
for Contador := 0 to Pred(Comandos.Count) do
begin
ICommand(Comandos[Contador]).Execute;
end;
end;
Â
O código do botão “Executar Comandos” (do Client), conforme já dito anteriormente, será incumbido de criar as instâncias das classes acima e configurar as interações entre elas:
var
Receiver: TReceiver;
Invoker: TInvoker;
begin
// Cria o Receiver (objeto que contém a codificação das operações)
Receiver := TReceiver.Create;
// Cria o Invoker (objeto que armazena os comandos para executá-los)
Invoker := TInvoker.Create;
try
// Armazena o comando de extração de programas no Invoker
Invoker.Add(TProgramas.Create(Receiver));
// Armazena o comando de extração de processos no Invoker
Invoker.Add(TProcessos.Create(Receiver));
// Armazena o comando de extração das variáveis de ambiente no Invoker
Invoker.Add(TVariaveisAmbiente.Create(Receiver));
// Solicita ao Invoker que execute todos os comandos armazenados
Invoker.ExtrairInformacoes;
finally
// Libera os objetos da memória
FreeAndNil(Invoker);
FreeAndNil(Receiver);
end;
MemoProgramas.Lines.LoadFromFile(GetCurrentDir + '\Programas.txt');
MemoProcessos.Lines.LoadFromFile(GetCurrentDir + '\Processos.txt');
MemoVariaveis.Lines.LoadFromFile(GetCurrentDir + '\Variaveis.txt');
end;
Ao clicar no botão, toda as extrações são realizadas em apenas uma ação, salvando-as em disco, e depois carregando-as nos componentes do formulário. Novamente, se quisermos atribuir outro Receiver ou adicionar/remover comandos, o mecanismo de execução, em sua integridade, continuará funcionando, visto que não há dependências entre as classes. Uma ideia seria adicionar um novo comando para enviar os arquivos gerados para o e-mail do suporte. 😉
Â
Eu sinto que você tem algumas ressalvas sobre o Command…
Tenho, sim. Você deve ter notado que este padrão de projeto estimula o “micro gerenciamento” de classes. Por um lado, é um ponto negativo por exigir a criação de várias classes, tornando a arquitetura um pouco volumosa. Por outro lado, cada classe tem uma responsabilidade pequena e única, promovendo o desacoplamento e facilitando a manutenção.
Além disso, você pode estar se perguntando: “Por que precisamos de um Invoker? Não bastaria apenas instanciar o Receiver e executar os três métodos em sequência?”. Sim, você pode. Se houver um formulário no sistema que exiba apenas a lista de programas instalados, só o Receiver realmente já é o suficiente. Porém, a empregabilidade do Command vai além dessa necessidade. O Invoker pode manter as instâncias armazenadas e executá-las em horários especÃficos, como um agendador de tarefas. Além disso, por ter controle da lista de comandos, o Invoker pode aplicar configurações adicionais, validações, controles de execução é até disponibilizar um recurso de desfazer comandos, que é mencionado em algumas literaturas do Command.
Â
Clique no link abaixo para baixar o projeto de exemplo deste artigo. Para personalizar a lista de comandos, adicionei três componentes TCheckBox, um para cada tipo de extração. Os comandos executados pelo Invoker serão apenas os que estão selecionados no formulário.
Experimente também adicionar novos Concrete Commands e/ou Receivers e observe a simplicidade de manutenção da arquitetura.
Â
Espero que o artigo tenha sido útil, leitores!
Um grande abraço a todos!
Parabéns André
Fica fácil aplicar padrões de projeto no dia-a-dia depois de ler o seus artigos.
Muito obrigado, Edson!
Esse é o objetivo! Fiquei contente com o seu comentário!
Abraço!
Muito bons seus artigos, porém este padrão ficou um pouco confuso para mim, a função da unit “Pattern.ConcreteCommand”
Abraço.
Olá, Fábio, tudo bem?
Compreendo a confusão, já que este padrão de projeto é um dos mais complexos.
Analisando o código, poderÃamos passar o Receiver diretamente para o Invoker, dispensando o Concrete Command, não é? Neste caso, o Concrete Command realmente aparenta ser desnecessário. No entanto, essa classe é responsável por aplicar possÃveis regras e condições antes ou depois de chamar os métodos do Receiver. Este comportamento não ocorre no projeto de exemplo, mas pode ser comum em projetos em produção.
Por exemplo, imagine que, após extrair a lista de processos, seja necessário enviá-la para algum e-mail. PoderÃamos codificar essa responsabilidade no Concrete Command:
Abraço!
Olá, muito bom o artigo, mas como ficaria para pegar o retorno por exemplo de uma classe que será executada via invoker?
EX: A classe abaixo precisava retornar se a nota foi emitida ou não!
Processamento.Add(ComandoEmitirNotaFiscal(Pedido));
Olá, Cassiano. Excelente pergunta!
A função do Invoker é exclusivamente empilhar a lista de comandos e executá-los posteriormente. Como todos os comandos são executados de uma só vez, não é possÃvel obter um retorno de uma das funções e tratá-lo de forma particular.
Sendo assim, uma ideia é levantar uma exceção dentro da classe de comando (ConcreteCommand) caso a nota não tenha sido emitida, de modo que a execução dos comandos seja interrompida.
Cassiano, dependendo das regras da rotina, talvez seja mais viável utilizar o Chain of Responsibility.
Abraço!