Design Patterns - Command

Autoras: Giovana Weihermann - GPT Silvia Angélica de Oliveira - GPS

1 Introdução

Segundo os especialistas, um dos benefícios da orientação à objetos (OO) é a reusabilidade de software. Porém, projetar software OO para que ele seja reusável não é uma tarefa fácil e, é praticamente impossível conseguir isto na primeira vez. O projeto da aplicação deveria ser específico para solucionar o problema em questão, mas por outro lado, geral o suficiente para ser reaproveitado futuramente em situações semelhantes.

Projetistas experientes percebem que determinadas soluções de projeto podem ser aplicadas em diferentes casos, logo, eles reusam essas soluções. Na verdade, acabamos identificando modelos (ou moldes) de classes e comunicação entre objetos que podem ser utilizados em muitos sistemas OO. Esses modelos resolvem problemas de projeto específicos e tornam os projetos OO mais flexíveis, elegantes e reusáveis. Um projetista que esteja familiarizado com os moldes pode aplicá-los imediatamente nos projetos sem ter que redescobrí-los.

Mas o que é Design Pattern? Segundo Christopher Alexander, cada pattern descreve um problema que ocorre várias vezes em nosso ambiente e o núcleo de uma solução para esse problema, de forma que se possa reutilizar esta solução diversas vezes.

Já tivemos a oportunidade de ter dois arquivos sobre pattern publicados no Bate Byte (Número 69 – Adapter Design Pattern e Número 75 – Composite Design Patterns), que nos mostram uma ótima introdução e o exemplo de dois Design Patterns. Vamos então pular a parte introdutória e detalhar mais um exemplo: o pattern Command.

2 Command

2.1 Propósito

Encapsular um pedido como um objeto, deste modo deixando você parametrizar os clientes com diferentes pedidos, filas ou logs e suportar operações que podem ser desfeitas.

2.2 Também conhecido como

Ação, transação.

2.3 Motivação

Algumas vezes é necessário enviar pedidos para objetos sem conhecimento nenhum sobre a operação que está sendo requisitada ou sobre o receptor. Por exemplo, ferramentas para geração de interface com usuário incluem objetos como botões e menus que realizam uma solicitação em resposta à entrada do usuário. Mas as ferramentas não podem implementar o pedido explicitamente no botão ou menu, porque somente aplicações que usam as ferramentas saberiam o que deveria ser feito naquele objeto. Como projetista das ferramentas, nós não temos maneira de conhecer o receptor da solicitação ou as operações que serão realizadas.

O pattern Command permite a objetos da ferramenta fazerem requisições de objetos não especificados de aplicações, através da transformação da própria requisição em um objeto. Este objeto pode ser armazenado e distribuído como outros objetos. A chave para este pattern é uma classe abstrata Command, que declara uma interface para execução de operações. Subclasses concretas Command especificam um par receptor-ação armazenando o receptor como uma instância variável e implementando ‘Execute’ para invocar a solicitação. O receptor tem o conhecimento necessário para executar o pedido.

wpe8.jpg (14154 bytes)

 

 

 

 

 

 

 

Menus podem ser implementados facilmente com os objetos Command. Cada escolha em um menu é uma instância da classe MenuItem. Uma classe Application cria estes menus e itens de menu ao longo da interface com o usuário. A classe Application também mantém-se informada sobre os objetos Document que um usuário abriu.

A aplicação configura cada MenuItem com uma instância de uma subclasse concreta Command. Quando o usuário seleciona um MenuItem, o MenuItem chama Execute nos seus comandos, e Execute realiza a operação. MenuItem não sabe qual subclasse do Command ele usa. A subclasse Command guarda a resposta do pedido e invoca uma ou mais operações no receptor.

Por exemplo, PasteCommand suporta passar um texto da área de transferência para dentro de um documento. O receptor do PasteCommand é o objeto Document que é fornecido pela instanciação. A operação Execute invoca Paste no Document receptor.

 

 

wpe9.jpg (11456 bytes)

 

 

 

 

 

 

 

Algumas vezes um MenuItem precisa executar uma seqüência de comandos. Por exemplo, um MenuItem para centralizar uma página em tamanho normal poderia ser construído por um objeto ‘ComandoCentralizarDocumento’ e um objeto ‘ComandoTamanhoNormal’. Porque é comum encadear comandos juntos e desta maneira, nós podemos definir uma classe de MacroCommand permitindo que um MenuItem execute números variados de comandos. MacroCommand é uma subclasse concreta de Command que simplesmente executa uma seqüência de comandos. MacroCommand não tem receptor explicitamente, porque a seqüência dos seus comandos define seus próprios receptores.

wpeA.jpg (10594 bytes)

2.4 Aplicabilidade

Use o pattern Command quando você quer:

  • parametrizar objetos por uma ação a realizar, como objetos MenuItem visto anteriormente. Você pode expressar tal parametrização numa linguagem procedural com uma função ‘Callback’, isto é, uma função que é registrada em algum lugar para ser chamada mais tarde.

  • Especificar, enfileirar e executar pedidos em tempos diferentes. Se o receptor do pedido puder ser representado de uma maneira independente do ‘address space’ (espaço de endereçamento), então você pode transferir um objeto Command desta requisição para um processo diferente e completar lá a solicitação.

  • Suportar ‘undo’ (desfazer). A operação Execute do Command pode armazenar o estado para reverter seus efeitos no próprio comando. A interface Command deve ter uma operação adicional ‘Unexecute’ que reverte os efeitos de uma chamada prévia para Execute.

  • Estruturar um sistema em volta de operações de alto nível construídas sobre operações primitivas. Tal estrutura, é comum em sistemas de informação que suportam transações. Uma transação encapsula um conjunto de alterações de dados.

2.5 Estrutura

 

 

wpeB.jpg (12894 bytes)

 2.6 Participantes

Command - declara uma interface para executar uma operação.

ConcreteCommand (PasteCommand, OpenCommand) - Define um encadeamento entre um objeto receptor e uma ação; Implementa Execute invocando operações correspondentes no receptor.

Client (Application) - Cria um objeto ConcreteCommand e estabelece seu receptor.

Invoker (MenuItem) - Pergunta o comando para realizar o pedido.

Receiver (Document, Application) - Sabe como realizar uma operação associada com o cumprimento de um pedido. Qualquer classe pode servir como receptor.

2.7 Colaborações

  • O Client cria um objeto ConcreteCommand especificando seu receptor.

  • Um objeto Invoker armazena o objeto ConcreteCommand.

  • O Invoker distribui uma solicitação chamando Execute sobre o comando. Quando o comando está pronto para voltar uma ação, ConcreteCommand guarda o estado para voltar para o comando anterior invocando Execute.

  • O objeto ConcreteCommand invoca operações nos seus receptores para cumprirem o pedido.

2.8 Conseqüências

O uso do pattern Command apresenta as seguintes conseqüências:

  • O Command separa o objeto que invoca a operação daquele que sabe executá-la.

  • Commands são objetos de primeira classe. Eles podem ser manipulados ou estendidos como qualquer outro objeto.

  • Você pode montar um comando composto a partir de outros comandos. Um exemplo é a classe MacroCommand descrita mais tarde. Em geral, comandos compostos são uma instância do pattern Command.

  • É fácil adicionar novos comandos, porque você não precisa mudar as classes existentes.

2.9 Implementação

Considere as seguintes questões quando implementar o pattern Command:

2.9.1 Quão inteligente um comando deveria ser?

Um comando pode ter um grande número de habilidades. Num extremo, ele meramente define um binding (ligação) entre um receptor e as ações que atendam o pedido. Em outro extremo, ele mesmo implementa tudo sem delegar ao receptor. O último extremo é útil quando você quer definir comandos que são independentes de classes existentes, quando receptores apropriados não existem, ou quando o comando conhece seu receptor implicitamente. Por exemplo, um comando que cria outra janela de aplicação pode ser capaz de criar a janela como qualquer outro objeto.

2.9.2 Suportando "Undo" e "Redo"

Comandos podem suportar "Undo" e "Redo" se eles proverem um caminho para reverter suas execuções (p.ex., uma operação Unexecute ou Undo). Uma classe ConcreteCommand pode precisar armazenar um estado adicional para fazer isso. Este estado pode incluir:

  • O objeto receptor, que de fato carrega operações em reposta ao pedido;

  • Os argumentos para as operações executarem no receptor, e ;

  • Qualquer valor original no recebedor que possa mudar como um resultado de lidar com o pedido. O receptor deve prover operações que permitam que o comando retorne o receptor para seu estado anterior.

Por exemplo, um DeleteCommand que exclui objetos selecionados deve armazenar diferentes conjuntos de objetos cada vez que ele é executado. Conseqüentemente, o objeto DeleteCommand deve ser copiado seguindo a execução, e a cópia é colocada na lista de histórico. Se o estado do comando nunca mudar na execução, então a cópia não é necessária, somente uma referência ao comando precisa ser incluída na lista de histórico. Comandos que devem ser copiados antes de serem incluídos na lista de histórico agem como protótipos.

2.9.3 Evitando acumulação de erros no processo "Undo".

Hysteresis pode ser um problema em assegurar a confiança, e preservar a semântica do mecanismos de "Undo"/"Redo". Erros podem ser acumulados quando comandos são executados, desfeitos e re-executados repetidamente, de modo que o estado da aplicação eventualmente divirja dos valores originais. Pode ser necessário, por esta razão, armazenar mais informação no comando para assegurar que os objetos serão restaurados ao seu estado original. O pattern Memento pode ser aplicado para dar o comando de acesso para estas informações sem expor o interior de outros objetos.

2.9.4 Usando templates C++

Para comandos incapazes de serem desfeitos e que não requerem argumentos, nós podemos usar templates C++ para evitar a criação de uma subclasse Command para cada tipo de ação e receptor. Nós mostramos como fazer isso na seção abaixo.

2.10 Exemplo de código

O código C++ mostrado aqui esboça a implementação das classes Command da seção motivação. Nós definiremos o OpenCommand, PasteCommand e MacroCommand. Primeiro a classe abstrata Command:

Class Command {

public:

virtual Command();

virtual void Execute() = 0;

protected:

Command();

};

O OpenCommand abre um documento cujo nome é fornecido pelo usuário. No construtor do OpenCommand deve ser passado um objeto Application. Askuser é a implementação de uma rotina para obter do usuário o nome do documento que será aberto.

 

class OpenCommand:public Command {

public:

OpenCommand(Application*);

virtual void Execute();

protected:

virtual const char* AskUser();

private:

Application* _application;

char* _response;

}

OpenCommand :: OpenCommand (Application* a) {

_application = a;

}

void OpenCommand :: Execute() {

const char* name = AskUser();

if (name != 0) {

Document* document = new Document(name);

_application -> Add(document);

document->Open();

}

}

Para o PasteCommand deve ser passado um objeto Document como seu receptor. O receptor é passado como parâmetro no construtor do PasteCommand.

class PasteCommand:public Command {

public:

PasteCommand(Document*);

virtual void Execute();

private:

Document* _document;

};

PasteCommand :: PasteCommand (Document* doc) {

_document = doc;

}

void PasteCommand :: Execute () {

_document->Paste();

}

Para comandos simples que não podem ser desfeitos e não requerem argumentos, nós podemos usar uma classe template para parametrizar o receptor do comando. Nós definiremos um template para uma subclasse SimpleCommand para comandos semelhantes. O SimpleCommand é parametrizado pelo tipo receptor e mantém um binding entre um objeto receptor e uma ação armazenada como um ponteiro para uma função membro.

template <class Receiver>

class SimpleCommad : public Command {

public:

typedef void (Receiver :: * Action) ();

SimpleCommand (Receiver* r, Action

a) :

_receiver(r), _action(a) { }

virtual void Execute();

private:

Action _action;

Receiver* _receiver;

};

O construtor armazena o receptor e a ação nas variáveis da instância correspondente. O método Execute simplesmente aplica a ação no receptor.

template <class Receiver>

void

SimpleCommand<Receiver>::Execute() {

(_receiver->*_action) ();

}

Para criar um comando que chama Action em uma instância da classe Myclass, um cliente simplesmente escreve:

MyClass * receiver = new MyClass;

// ....

Command* aCommand =

new SimpleCommand<MyClass>(receiver, &MyClass::Action);

// ....

aCommand->Execute();

Lembre-se que esta solução funciona somente para comandos simples. Comandos mais complexos que guardam não somente seus receptores mas também os argumentos e/ou estado de "Undo" requerem uma subclasse Command.

Um MacroCommand gerencia uma seqüência de subcomandos e provê operações para adicionar e remover subcomandos. Nenhum receptor explícito é requerido, porque os subcomandos já definem seus receptores.

class MacroCommand:public Command {

public:

MacroCommand();

virtual ~MacroCommand();

virtual void Add(Command*);

virtual void Remove(Command*);

virtual void Execute();

private:

List<Command*>* _cmds;

}

A chave para o MacroCommand é sua função membro Execute. Esta percorre todos os subcomandos e executa Execute sobre cada um deles.

void MacroCommand :: Execute() {

ListIterator<Command*> i(_cmds);

for (i.First(); !i.IsDone(); i.Next()) {

Command* c = i.CurrentItem();

c->Execute();

}

}

Note que o MacroCommand deve implementar uma operação Unexecute, então seus subcomandos devem ser desfeitos na ordem inversa relativa à implementação do Execute.

Finalmente, MacroCommand deve prover operações para gerenciar seus subcomandos. O MacroCommand é também responsável por deletar seus subcomandos.

void MacroCommand::Add (Command* c) {

_cmds->Append(c);

}

void MacroCommand :: Remove (Command* c) {

_cmds->Remove(c);

}

2.11 Usos conhecidos

Talvez o primeiro exemplo do pattern Command apareça em um paper de Lieberman. MacApp popularizou a noção de comandos para implementar operações capazes de serem desfeitas. ET++, InterViews, e Unidraw também definiram classes que seguem o pattern Command. InterViews define uma classe abstrata Action que provê funcionalidades aos comandos. Ele também define um template ActionCallBack, parametrizado pelo método Action, que pode instanciar comandos de subclasse automaticamente.

A biblioteca de classes THINK também usa comandos para suportar ações incapazes de serem desfeitas. Comandos em THINK são chamados de ‘tasks’ (tarefas). Objetos tasks são passados para a Cadeia de Responsabilidade para consumação.

Os comandos dos objetos Unidraw são os únicos que podem se comportar como mensagens. Um comando Unidraw pode ser enviado para outro objeto para interpretação, e o resultado da interpretação varia com o objeto receptor. Além disso, o receptor pode delegar a interpretação para outro objeto, tipicamente o pai do receptor em uma estrutura maior como em uma cadeia de responsabilidade. O receptor de um comando Unidraw é deste modo computado preferivelmente do que armazenado. O mecanismo de interpretação do Unidraw depende de informação do tipo em execução.

Coplien descreve como implementar functors, objetos que são funções, em C++. Ele consegue um grau de transparência em seu uso pelo overloading da chamada de função (operator()). O pattern Command é diferente; seu foco é em manter o binding entre um receptor e uma função (p.ex., ação), não exatamente mantendo uma função.

2.12 Patterns mencionados

Um Composite pode ser usado para implementar MacroCommands.

Um Memento pode guardar o estado do comando requerido para desfazer seu efeito.

Um comando que deve ser copiado antes de ser colocado na lista do histórico de ações como um Prototype.

3 Um Caso de Estudo: Projetando um Editor de Documentos

Esta parte do trabalho mostra um caso de estudo de um projeto de um editor de documentos ‘O que você vê é o que você tem (WYSIWYG)’ chamado Lexi. Veremos como o molde de projeto ‘Command’ captura soluções para projetar problemas no Lexi e aplicações como ele.

O documento pode misturar textos e figuras livremente numa variedade de estilos. Em volta do documento estão os menus "pull-down" e as barras de rolagem, mais uma coleção de ícones das páginas que pulam para uma página particular no documento.

3.1 Operações dos Usuários

Algumas funcionalidades do Lexi estão disponíveis através da representação WYSIWYG do documento. Você insere e deleta um texto, move um ponto inserido e seleciona partes de texto apontando-o ou clicando sobre ele e digita diretamente no documento. Outras funcionalidades são acessadas indiretamente através de operações dos usuários nos menus "pull-down", botões e aceleradores de teclado. As funcionalidades incluem operações para:

  • Criar um novo documento;

  • Abrir, salvar e imprimir um documento existente;

  • Cortar e inserir um texto no documento;

  • Mudar a fonte e estilo de um texto selecionado;

  • Sair da aplicação;

  • Etc.

Lexi oferece interfaces diferentes para estas operações. Mas não queremos associar uma operação particular com uma interface particular, porque podemos querer múltiplas interfaces para a mesma operação (você pode virar uma página do documento usando um botão da página ou uma operação no menu, por exemplo). Podemos também querer mudar a interface no futuro. De outra maneira terminaremos com uma implementação firmemente acoplada, que seria mais difícil para compreender, ampliar e manter.

Para complicar mais os problemas, queremos que Lexi suporte ‘Undo’ (Desfazer) e ‘Redo’ (Refazer) da maioria "mas não todas" suas funcionalidades. Especificamente, queremos que seja possível desfazer operações que modificam o documento, como delete, em que o usuário pode destruir muitos dados sem querer. Mas não deveríamos possibilitar desfazer uma operação como salvar um desenho ou sair da aplicação. Estas operações não teriam efeito sobre o processo ‘Undo’. Também não queremos um limite arbitrário dos números de níveis para "Undo" e "Redo".

É certo que operações de suporte a usuários estão presentes por toda a aplicação. O desafio é propor um mecanismo simples e amplo que satisfaça todas estas necessidades.

3.2 Encapsulando uma Requisição

Das nossas perspectivas como projetistas, um menu ‘pull-down’ é somente outro tipo de símbolo gráfico que contém outro símbolo. O que distingue um menu ‘pull-down’ de outros símbolos que têm ‘filhos’ é que a maioria dos símbolos nos menus fazem alguns trabalhos em resposta de um click.

Vamos assumir que estes símbolos realizadores dos trabalhos são instâncias de uma subclasse ‘Símbolo’ chamada ‘MenuItem’ e que eles fazem seus trabalhos em resposta a uma solicitação de um cliente (conceitualmente o cliente é o usuário do Lexi, mas na realidade é um outro objeto – como despachante de um evento – que gerencia entradas de usuários. Executar uma solicitação pode envolver uma operação sobre um objeto ou muitas operações sobre muitos objetos, ou alguma coisa intermediária.

Poderíamos definir uma subclasse do MenuItem para todas as operações do usuários. Mas isto não é realmente certo; não precisamos de uma subclasse do MenuItem para cada requisição mais do que precisamos de uma subclasse para cada texto encadeado num menu ‘pull-down’. Além disso, esta abordagem une a requisição a uma interface de usuário em particular, tornando difícil satisfazer o pedido através de uma interface diferente.

O que está faltando é um mecanismo que deixa você parametrizar itens de menu pelas requisições que eles deveriam realizar. Desta maneira nós evitamos a proliferação de subclasses e permitimos por maior flexibilidade em tempo de execução. Poderíamos parametrizar MenuItem com uma função para chamar, mas que não é uma solução completa por no mínimo estas três razões:

  • Isto não enfoca o problema "Undo"/"Redo";

  • É difícil associar estado com uma função. Por exemplo, uma função que muda fontes precisa saber ‘qual é a fonte’;

  • Funções são difíceis de ampliar e é difícil reutilizar partes delas.

Estas razões sugerem que nós deveríamos parametrizar ‘MenuItem’ com um objeto, não uma função. Então nós podemos usar herança para ampliar e reusar as implementações da requisição. Também temos um lugar para guardar estados e implementar funcionalidades de Undo/Redo. Aqui temos outro exemplo do encapsulamento dos conceitos que variam, neste caso um pedido. Nós encapsulamos cada solicitação num objeto ‘Command’.

3.3 Undoability (Capacidade de Desfazer)

Undo/Redo é uma importante capacidade em aplicações interativas. Para desfazer e refazer comandos, adicionamos uma operação ‘Unexecute’ para a interface do ‘Command’. ‘Unexecute’ reverte os efeitos de uma operação ‘Execute’ precedente usando qualquer informação para desfazer ‘Execute’. No caso da FontCommand, por exemplo, a operação Execute armazenaria a extensão de texto afetado pela troca da fonte com a fonte original. A operação Unexecute do FontCommand restauraria a extensão de texto para sua fonte original.

Algumas vezes, UNDOABILITY deve ser determinado em tempo de execução. Uma requisição para mudar a fonte de uma seleção não faz nada se o texto já aparece com aquela fonte. Suponha que o usuário seleciona algum texto e então requer uma troca "spurious" de fonte. Qual deveria ser o resultado de subsequentes requisições de undo? Uma mudança insignificante deveria causar o undo da requisição para fazer algo igualmente insignificante? Provavelmente não.

Se o usuário repete a troca ‘spurious’ da fonte diversas vezes, ele não precisaria realizar exatamente o mesmo número de operações undo para obter a última operação significativa novamente. Se o efeito da execução de um comando não foi nada, então não há necessidade de uma requisição undo correspondente. Então, para determinar se um comando pode ser desfeito, adicionamos uma operação abstrata ‘Reversible’ para a interface ‘Command’. ‘Reversible’ retorna um valor boolean. Subclasses podem redefinir esta operação para que um critério retorne verdadeiro ou falso em tempo de execução.

3.4 Command Pattern

Comandos do Lexi estão numa aplicação do Command pattern, que descreve como encapsular uma requisição. O pattern Command prescreve uma interface uniforme para emitir requisições que deixam você configurar clientes para manupular diferentes requisições. A interface isola o cliente da implementação de uma requisição. Um comando pode delegar toda, parte ou nenhuma implementação da requisição para outro pedido. Isto é perfeito para aplicações como Lexi que devem fornecer acessos centralizados para funcionalidades presentes por toda a aplicação. O pattern também discute mecanismos de undo e redo construídos sobre a interface ‘Command’ básica.

4 Conclusão

A reusabilidade não é obtida pelo simples fato de desenvolvermos softwares orientados a objetos. O software deve ser planejado para ser reusável. Design Patterns é um mecanismo de alto nível que reúne experiências de sucesso para resolver problemas que notadamente se repetem em diversos projetos de software. Permite que os projetistas utilizem uma linguagem de alto nível para discutirem sobre o projeto, porque o simples nome de um pattern representa um problema e como solucioná-lo. A utilização de patterns requer experiência do projetista em orientação a objetos, para que o mesmo possa identificar no seu projeto em particular onde os patterns podem ser aplicados.

Referências Bibliográficas

GAMMA, Erich; HELM, Richard; JOHNSON, Ralph; VLISSIDES, John. Design patterns: elements of reusable object-oriented software. Massachusetts : Addison-Wesley, 1995.