Padrão Command
Last updated
Last updated
O Padrão Command encapsula uma solicitação (chamada de método) como um objeto, o que lhe permite parametrizar outros objetos com diferentes solicitações, enfileirar ou registrar solicitações e implementar recursos de cancelamento de operações.
A principal motivação do uso do padrão Command é que algumas vezes é necessário emitir solicitações para objetos sem nada saber sobre a operação que está sendo solicitada ou sobre quem será o seu receptor.
Imagine que queremos criar um sistema automatizado de casa inteligente onde seja possível: ligar a TV ao chegar em casa, Ligar o sistema de som em um horário programado, ligar o ar-condicionado ao entrar em um ambiente, etc. Acontece que nesse contexto, uma vez que cada um dos aparelhos vem de uma marca diferente, a variedade de interfaces com as quais precisamos nos comunicar é maior.
Imagine nosso sistema e cada aparelho como uma classe distinta, apenas para fins didáticos. Cada aparelho tem métodos diferentes, com parâmetros diferentes, o que faria com que nosso sistema criasse um alto acoplamento com cada um dos aparelhos. O exemplo de código abaixo facilmente seria implementado nesse cenário.
Porém, queremos que nosso sistema seja flexível para que possa se comunicar com novos aparelhos e também com aparelhos de outras marcas com interfaces diferentes.
Como implementar um sistema com estas características?
Que evite acoplamento entre o invocador (sistema automatizado) e o objeto que, de fato, implementa a operação (aparelho de TV, som, etc).
Que seja possível adicionar novos dispositivos sem que o sistema automatizado crie um novo acoplamento
Se queremos que nosso sistema seja extensível e suporte outros tipos de dispositivos, temos que enxergar eles da mesma forma, abstraindo os detalhes e passando a lidar apenas com módulos de alto nível.
O Padrão Command tem como intenção "Encapsular uma requisição como um objeto, permitindo que clientes parametrizem diferentes requisições, filas ou requisições de log, e suportar operações reversíveis". Sendo assim, nosso Invocador só precisa se relacionar com esse objeto que representa uma requisição. Vamos ao código
Criamos a interface para os objetos de requisição, ou comandos, como vamos chamar aqui. A partir daqui, cada objeto vai encapsular uma das chamadas como esta abaixo.
Mas e como iremos saber qual o parâmetro correto pra cada uma das requisições? E se eu quiser trocar a temperatura do condicionador de ar ou o canal que deve ser definido para a TV? Vamos visualizar como esses comandos serão implementados.
Repare nos parâmetros que recebemos no construtor, o objeto do tipo TV
que será invocado e os parâmetros necessários para essa invocação, no caso o canal
.
Agora podemos refatorar o Invocador
para que ele não precise mais criar acoplamento com esses dispositivos, e enxergue apenas a interface Comando
.
E quando quisermos adicionar novos dispositivos, essa classe não precisará ser alterada para estar pronta para as novas funcionalidades. Criaremos apenas um novo comando, que irá receber o objeto Receptor (TV, Som, etc...) e seus parâmetros.
Observe o diagrama de classes abaixo. O Client
é qualquer entidade de software que queira utilizar o Invocador (Invoker
no diagrama), que por sua vez só conhece a interface Command
. Os comandos são de fato implementados nas classes ConcreteCommand
, no nosso exemplo seriam LigarArCondicionado
e LigarTV
. Por fim, o Receiver
é qualquer classe concreta dos dispositivos, que pode ter interfaces diversas, mas que no final só serão invocadas pelos comandos.
Nas APIs nativas do Java, podemos encontrar um exemplo do padrão Command nos componentes que implementam a interface Runnable
, como é o caso de TimerTask
. No exemplo abaixo, cada implementação de TimerTask
, feito por meio de classes anônimas executa um método run()
que encapsula uma solicitação que deve acontecer de tempos em tempos.
O componente Timer
por sua vez, agenda esses objetos do tipo TimerTask
para que sejam executados repetidamente dentro de um espaço de tempo (linhas 19 e 20), mas sem saber qual é a operação que de fato vai ser executada, uma vez que conhece apenas a abstração TimerTask
.