Padrões de design: comando

Carlos Caballero em HackerNoon.com Seguir Mai 23 · 7 min ler

Existem 23 padrões de design clássico, descritos no livro original, Design Patterns: Elements of Reusable Object-Oriented Software . Esses padrões fornecem soluções para problemas específicos, frequentemente repetidos no desenvolvimento de software.

Neste artigo, vou descrever como o padrão de comando; e como e quando deve ser aplicado.

Padrão de Comando: Idéia Básica

Na programação orientada a objetos, o padrão de comando é um padrão de design comportamental no qual um objeto é usado para encapsular todas as informações necessárias para executar uma ação ou acionar um evento posteriormente. Esta informação inclui o nome do método, o objeto que possui o método e os valores para os parâmetros do método – Wikipedia

Encapsule uma solicitação como um objeto, permitindo assim parametrizar clientes com solicitações diferentes, enfileirar ou registrar solicitações, e dar suporte a operações que podem ser desfeitas – Padrões de design: elementos do software orientado a objeto reutilizável

Nesse padrão, uma classe Command abstrata é declarada como uma interface para executar operações. A classe Command define um método chamado execute , que deve ser implementado em cada comando concreto. Este método de execute é uma ponte entre um objeto Receiver e uma ação. O Receiver sabe como executar as operações associadas a uma solicitação (qualquer classe pode ser um Receiver ). Outro componente relevante neste padrão é a classe Invoker que solicita o comando que deve ser executado.

O diagrama UML para esse padrão é o seguinte:

O padrão de comando deve ser usado quando:

  1. Você precisa de um comando para ter um tempo de vida independente da solicitação original. Além disso, se você deseja enfileirar, especificar e executar solicitações em momentos diferentes.
  2. Você precisa desfazer / refazer as operações. A execução do comando pode ser armazenada para reverter seus efeitos. É importante que a classe Command implemente os métodos desfazer e refazer.
  3. Você precisa estruturar um sistema em torno de operações de alto nível baseadas em operações primitivas.

O padrão de comando tem várias vantagens, resumidas nos seguintes pontos:

  • Ele separa as classes que invocam a operação do objeto que sabe como executar a operação
  • Ele permite que você crie uma seqüência de comandos, fornecendo um sistema de fila
  • Implementar extensões para adicionar um novo comando é fácil e pode ser feito sem alterar o código existente.
  • Você também pode definir um sistema de reversão com o padrão Command, como no exemplo do Assistente, poderíamos escrever um método de reversão.
  • Tenha controle rigoroso sobre como e quando os comandos são invocados.
  • O código é mais fácil de usar, entender e testar, pois os comandos simplificam o código.

Agora vou mostrar como você pode implementar esse padrão usando JavaScript / TypeScript. No nosso caso, stockTrade um problema no qual existe uma classe chamada Agent que define os atributos: stockTrade ; e uma operação placeOrder . Esta classe é a ponte entre o cliente / contexto e o StockTrader . O método placeOrder é responsável por decidir qual ação deve ser executada. Por exemplo, se o orderType for buy ou sell o método deve invocar a ação no StockTrader . O diagrama UML a seguir mostra o cenário que acabei de descrever.

Os códigos do client e do Agent são os seguintes:

O cheiro de código mais relevante é o método placeOrder , que é acoplado às ações / comandos da StockTrade . Existem diferentes técnicas para evitar esse cheiro de código. Nesse caso, o padrão Command é uma boa solução, já que queremos registrar o histórico do comando.

Finalmente, a classe StockTrade é a seguinte:

O resultado obtido é mostrado na imagem a seguir:

Padrão de Comando – Exemplo 1: Um Mercado de Ações – Solução

A ideia de dissociar os comandos da classe Agent é criar um conjunto de classes para cada comando. No entanto, os comandos compartilham uma interface comum que nos permite executar a ação dependendo de cada comando concreto.

Essa é a razão pela qual criamos a classe abstrata Order que terá um método abstrato chamado execute . Este método é aquele que será invocado da classe Agent (o invocador). Além disso, a classe Agent terá uma lista de comandos para obter o histórico do comando.

Desta forma, o agente delega a responsabilidade de saber qual operação deve ser executada no objeto que recebe. A principal mudança é que a classe Agent não receberá mais um atributo primitivo como um parâmetro (string), já que isso não tem valor semântico. Em vez disso, a classe Agent receberá agora um objeto de comando como um parâmetro, que fornece valor semântico.

O novo diagrama UML usando o padrão de comando é mostrado abaixo:

O código associado ao client é o seguinte:

Neste caso, cada order recebe o StockTrade usando DI (Injeção de Dependência). O Agent chama o comando usando o método placeOrder , que executa a operação através do método de execute .

O código associado ao Agent é o seguinte:

Você pode observar que a estrutura de controle if-elseif-else é evitada usando o método order.execute , que delega a responsabilidade a cada comando.

O código associado ao Order e a cada pedido são os seguintes:

A classe StockTrade não é modificada neste comando. Portanto, o resultado após essas modificações na execução do programa é mostrado na imagem a seguir:

npm run example1-problem
npm run example1-command-solution1

Outro exemplo interessante que é resolvido usando o padrão de comando é quando há vários comandos para executar para um robô.
Por exemplo, um conjunto de comandos como
SaveSecret , Clean and Move são pedidos a um robô famoso, o R2D2. No diagrama UML a seguir, você pode ver essa situação:

O código associado aos clientes é o seguinte:

Neste exemplo, existem três comandos (saveSecretCommand, cleanCommand e moveCommand), dois serviços (StoreService e R2D2Service) e um Agente (R2D2).

O Agente invoca as ordens usando o método executeCommand que recebe dois argumentos: 1) O comando; 2) Os parâmetros para executar o comando anterior.

Portanto, o código associado ao R2D2 é o seguinte:

R2D2 tem uma lista de comandos, que podem ser listados através do método listCommands , e armazenados usando a estrutura de dados de commands . Finalmente, o método executeCommand é responsável por invocar o método de execução de cada comando.

Então, o próximo passo é criar o código associado ao comando (classe abstrata) e a cada comando concreto:

Finalmente, cada comando invoca o serviço responsável pela ação, neste caso usamos dois serviços diferentes para mostrar que nem todos os comandos delegam responsabilidade ao mesmo serviço ou classe.

O resultado obtido é mostrado na imagem a seguir:

Eu criei um npm scripts que executam o exemplo mostrado aqui depois de aplicar o padrão de comando.

npm run example2-command-solution-1

O padrão de comando pode evitar a complexidade em seus projetos porque você encapsula os comandos em classes específicas que podem ser adicionadas / removidas ou alteradas a qualquer momento (incluindo tempo de execução).

O mais importante é não implementar o padrão como mostrei, mas ser capaz de reconhecer o problema que esse padrão específico pode resolver e quando você pode ou não implementar esse padrão. Isso é crucial, pois a implementação irá variar dependendo da linguagem de programação usada.

A ramificação do GitHub deste post é https://github.com/Caballerog/blog/tree/master/command-pattern

Texto original em inglês.