Introdução ao Padrão de Comando: Em comparação com outros padrões de projeto, o padrão de comando não possui tantas restrições, não sendo um padrão muito "rígido". Por essa razão, o padrão de comando é mais flexível em relação a outros padrões de design. Um exemplo comum do padrão de comando são os comandos de menu, como em sistemas operacionais, onde ao clicar no comando "Desligar", o sistema executa uma série de operações: primeiro, pausa o processamento de eventos, salva algumas configurações do sistema, encerra os processos dos programas e, finalmente, invoca o comando do kernel para desligar o computador. O padrão de comando funciona de maneira semelhante.
Definição do Padrão de Comando: Encapsular uma solicitação como um objeto, permitindo que diferentes solicitações sejam parametrizadas para o cliente, além de possibilitar enfileirar solicitações ou registrar logs de operações.
Cenários de Uso do Padrão de Comando: Quando é necessário abstrair ações a serem executadas e fornecê-las como parâmetros - semelhante a callbacks em programação procedural, sendo o padrão de comando a implementação orientada a objetos desses callbacks.
O padrão de comando foca principalmente na relação entre o solicitante do comando e seu executor, onde o comando contém uma referência ao executor.
A seguir, usaremos o jogo "Tetris" como exemplo para explicar a aplicação do padrão de comando.
No jogo Tetris, existem 4 botões: dois para movimentação lateral (esquerda e direita), um para queda rápida e outro para transformar a forma da peça. Um jogador corresponde ao nosso cliente, enquanto os 4 botões do jogo correspondem a 4 solicitantes ou invocadores. A lógica específica de execução dos comandos dos botões pode ser considerada o papel do comando.
A seguir, ilustraremos com código. Primeiro, o receptor do comando:
1 /**
2 * Receptor do comando, máquina do jogo Tetris
3 *
4 */
5 public class MaquinaTetris {
6
7 // Lógica real para processar a operação "mover para a esquerda"
8 public void moverEsquerda(){
9 System.out.println("Movendo para a esquerda");
10 }
11
12 // Lógica real para processar a operação "mover para a direita"
13 public void moverDireita(){
14 System.out.println("Movendo para a direita");
15 }
16
17 // Lógica real para processar a operação "queda rápida"
18 public void quedaRapida(){
19 System.out.println("Queda rápida");
20 }
21
22 // Lógica real para processar a operação "transformar forma"
23 public void transformar(){
24 System.out.println("Transformando forma");
25 }
26 }
A classe MaquinaTetris é o único lugar no padrão de comando que processa a lógica específica do código. Todas as outras classes chamam direta ou indiretamente os métodos dessa classe, que é o papel do receptor - processar a lógica específica.
Em seguida, definimos uma interface de comando como abstração para o papel do comando:
1 /**
2 * Abstração para o papel do comando
3 */
4 public interface Comando {
5 // Método de execução do comando
6 void executar();
7 }
Agora, implementamos os 4 comandos:
1 /**
2 * Comando para mover para a esquerda
3 */
4 public class ComandoEsquerda implements Comando{
5 // Mantém uma referência ao executor do comando
6 MaquinaTetris maquinaTetris;
7
8 public void setMaquinaTetris(MaquinaTetris maquinaTetris){
9 this.maquinaTetris = maquinaTetris;
10 }
11
12 @Override
13 public void executar() {
14 // Chama o método da máquina de jogo para execução específica
15 maquinaTetris.moverEsquerda();
16 }
17 }
1 /**
2 * Comando para mover para a direita
3 */
4 public class ComandoDireita implements Comando{
5 // Mantém uma referência ao executor do comando
6 MaquinaTetris maquinaTetris;
7
8 public void setMaquinaTetris(MaquinaTetris maquinaTetris){
9 this.maquinaTetris = maquinaTetris;
10 }
11
12 @Override
13 public void executar() {
14 // Chama o método da máquina de jogo para execução específica
15 maquinaTetris.moverDireita();
16 }
17 }
1 /**
2 * Comando para queda rápida
3 */
4 public class ComandoQuedaRapida implements Comando{
5 // Mantém uma referência ao executor do comando
6 MaquinaTetris maquinaTetris;
7
8 public void setMaquinaTetris(MaquinaTetris maquinaTetris){
9 this.maquinaTetris = maquinaTetris;
10 }
11
12 @Override
13 public void executar() {
14 // Chama o método da máquina de jogo para execução específica
15 maquinaTetris.quedaRapida();
16 }
17 }
1 /**
2 * Comando para transformar forma
3 */
4 public class ComandoTransformar implements Comando{
5 // Mantém uma referência ao executor do comando
6 MaquinaTetris maquinaTetris;
7
8 public void setMaquinaTetris(MaquinaTetris maquinaTetris){
9 this.maquinaTetris = maquinaTetris;
10 }
11
12 @Override
13 public void executar() {
14 // Chama o método da máquina de jogo para execução específica
15 maquinaTetris.transformar();
16 }
17 }
Acima temos os 4 comandos correspondentes. Agora implementamos um solicitante de comando:
1 /**
2 *
3 * Solicitante do comando
4 */
5 public class Controles {
6 // Comando para mover para a esquerda
7 private ComandoEsquerda comandoEsquerda;
8 // Comando para mover para a direita
9 private ComandoDireita comandoDireita;
10 // Comando para queda rápida
11 private ComandoQuedaRapida comandoQuedaRapida;
12 // Comando para transformar forma
13 private ComandoTransformar comandoTransformar;
14
15 // Define o comando para mover para a esquerda
16 public void setComandoEsquerda(ComandoEsquerda comandoEsquerda) {
17 this.comandoEsquerda = comandoEsquerda;
18 }
19
20 // Define o comando para mover para a direita
21 public void setComandoDireita(ComandoDireita comandoDireita) {
22 this.comandoDireita = comandoDireita;
23 }
24
25 // Define o comando para queda rápida
26 public void setComandoQuedaRapida(ComandoQuedaRapida comandoQuedaRapida) {
27 this.comandoQuedaRapida = comandoQuedaRapida;
28 }
29
30 // Define o comando para transformar forma
31 public void setComandoTransformar(ComandoTransformar comandoTransformar) {
32 this.comandoTransformar = comandoTransformar;
33 }
34
35 // Pressionar botão para mover para a esquerda
36 public void pressionarEsquerda(){
37 comandoEsquerda.executar();
38 }
39
40 // Pressionar botão para mover para a direita
41 public void pressionarDireita(){
42 comandoDireita.executar();
43 }
44
45 // Pressionar botão para queda rápida
46 public void pressionarQueda(){
47 comandoQuedaRapida.executar();
48 }
49
50 // Pressionar botão para transformar forma
51 public void pressionarTransformar(){
52 comandoTransformar.executar();
53 }
54
55 }
Finalmente, o cliente decide como usar:
Classe cliente:
1 /**
2 * Jogador, também o cliente
3 */
4 public class Jogador {
5 public static void main(String[] args){
6 testarPadrao();
7 }
8
9 // Testar o padrão de comando
10 private static void testarPadrao() {
11 // Jogo Tetris
12 MaquinaTetris maquinaTetris = new MaquinaTetris();
13
14 // Com base no jogo, criamos 4 tipos de comando
15 ComandoEsquerda comandoEsquerda = new ComandoEsquerda();
16 ComandoDireita comandoDireita = new ComandoDireita();
17 ComandoQuedaRapida comandoQuedaRapida = new ComandoQuedaRapida();
18 ComandoTransformar comandoTransformar = new ComandoTransformar();
19
20 // Referência ao executor específico de cada comando
21 comandoEsquerda.setMaquinaTetris(maquinaTetris);
22 comandoDireita.setMaquinaTetris(maquinaTetris);
23 comandoQuedaRapida.setMaquinaTetris(maquinaTetris);
24 comandoTransformar.setMaquinaTetris(maquinaTetris);
25
26 // Os controles podem executar diferentes comandos
27 Controles controles = new Controles();
28 controles.setComandoEsquerda(comandoEsquerda);
29 controles.setComandoDireita(comandoDireita);
30 controles.setComandoQuedaRapida(comandoQuedaRapida);
31 controles.setComandoTransformar(comandoTransformar);
32
33 // Qual botão pressionar é decisão do jogador
34 controles.pressionarEsquerda();
35 controles.pressionarDireita();
36 controles.pressionarQueda();
37 controles.pressionarTransformar();
38 }
39
40 // Para a maioria dos desenvolvedores, é mais fácil aceitar o código abaixo
41 private static void metodoAlternativo(){
42 // Jogo Tetris
43 MaquinaTetris maquina = new MaquinaTetris();
44
45 // Para implementar o controle desejado, basta chamar a função correspondente
46 maquina.moverEsquerda();
47 maquina.moverDireita();
48 maquina.quedaRapida();
49 maquina.transformar();
50 }
51
52
53 }
Após ler todo esse código, você pode estar se perguntando: por que um problema tão simples precisa ser tratado de forma tão complexa? Para a maioria dos desenvolvedores, seria mais aceitável usar o código do métodoAlternativo(), como:
// Para a maioria dos desenvolvedores, é mais fácil aceitar o código abaixo
41 private static void metodoAlternativo(){
42 // Jogo Tetris
43 MaquinaTetris maquina = new MaquinaTetris();
44
45 // Para implementar o controle desejado, basta chamar a função correspondente
46 maquina.moverEsquerda();
47 maquina.moverDireita();
48 maquina.quedaRapida();
49 maquina.transformar();
50 }
A lógica é tão complexa porque facilita o desenvolvimento. Cada vez que precisamos adicionar ou modificar recursos do jogo, só precisamos modificar a classe MaquinaTetris e, em seguida, ajustar a classe Jogador. Torna tudo muito conveniente. No entanto, o que é conveniente para o desenvolvedor pode não ser para quem vier depois. Um princípio importante dos padrões de projeto é: "fechado para modificação, aberto para extensão". Pode refletir sobre isso.