.NET - O padrão de projeto Command


O padrão de projeto (Design Pattern) Command é um padrão comportamental  cuja intenção é encapsular uma solicitação como um objeto e desta forma permitir que você parametrize clientes com diferentes solicitações, enfileire ou registre(log) solicitações e suporte operações que podem ser desfeitas.(GoF)

Podemos dizer que o padrão Command tem como função principal encapsular a invocações a objetos.

Nota: Padrões de projeto comportamentais são padrões que tratam das interações e divisões de responsabilidades entre as classes ou objetos.

O diagrama de classes para o padrão Command  é mostrado na figura abaixo:

Classes/Objetos participantes do padrão:
  1. Comando (Command):  Declara uma interface para executar uma operação; Esta classe abstrata é a classe base para todos os objetos Command. A classe também define um método abstrato que é usado pelo Chamador para executar comandos.
  2. ComandoConcreto (CommandConcret): Define uma vinculação entre um objeto Receptor e uma ação; Além de implementar o método Execute, ela contém todas as informações que são necessárias para executar corretamente a ação usando o objeto Receptor vinculado.
  3. Cliente (Client) - Cria um objeto ComandoConcreto e define o seu receptor; É o consumidor das classes do padrão Command;
  4. Chamador(Invoker) - Solicita ao comando para executar a requisição; Inicia a execução dos comandos;
  5. Receptor(Receiver) - Conhece como executar as operações associadas com a execução do comando; Objetos receptores contém os métodos que são executados quando um ou mais comandos são invocados. Isso permite que a funcionalidade real seja realizada separadamente para as definições de comando.

O mesmo diagrama usando os nomes originais:

 

Em tempo de execução o objeto Invoker(Chamador) chama o método execute() do objeto Command que delega ao método action() do objeto receiver que executará a operação.

Vantagens em usar o padrão Command:

Quando usar o padrão Command ?

Implementando o padrão de projeto Command

Para implementar um Command temos que fazer o seguinte:

  1. Criar uma interface chamada Command que define o método execute();
  2. Criar a classe Receiver que implementa os comportamentos reais;
  3. Criar a classe que implementa Command e encapsula os métodos de Receiver no método execute();
  4. Criar a classe Invoker, que possui um elemento Command e métodos que invocam execute();
  5. Criar a classe Client que trabalha com as opções de Invoker;

Para implementar o padrão Observer eu vou usar o seguinte cenário:

Imagine um robo sendo controlado à distância como o robo Curiosity que está em Marte.

Neste cenário os comandos ditam o movimento do robô e também são usados para realizar operações como recolher amostras da superfície de Marte.

Neste caso o Receptor seria o robô e o Chamador seria um sistema de software programado executado dentro do robô.

O Cliente seria controlado pelos engenheiros aqui na Terra que poderia criar uma fila de comandos. O robô também inclui uma função desfazer de forma a poder reverter alguns comandos conforme necessário.

Neste cenário iremos ter as seguintes classes:

  1. RoboCommand - Representa a classe abstrata Command onde definimos os método Executar() e Desfazer();
  2. MoverCommand , RotacionarCommand e EscavarCommand - Representam a classe CommandConcreto que herda da classe RoboCommand e implementa os comandos desejados;
  3. Robo - Representa o Receptor onde iremos definir as funcionalidades dos comandos;
  4. RoboControle - Representa o Chamador onde definimos uma fila de comandos e onde podemos dezfazer os comandos;

O Diagrama de classes gerado é visto abaixo:

Implementando o padrão Command

Vamos agora criar as classes e verificar o seu funcionamento.

Eu vou usar o Visual Studio 2012 Express Edition e criar uma aplicação Console chamada PadraoProjetoCommand;

Obs: Estou disponibilizando também o código para VB .NET criado no Visual Studio 2012 Express Edition

No menu Project clique em Add Class e informe o nome RoboCommand.cs/RoboCommand.vb e a seguir digite o código da classe conforme abaixo:


namespace PadraoProjetoCommand
{
    public abstract class RoboCommand
    {
       protected Robo _robo;

       public RoboCommand(Robo robo)
       {
           _robo = robo;
       }
 
       public abstract void Executar();
       public abstract void Desfazer();
    }
}

Public MustInherit Class RoboCommand
    Protected _robo As Robo

    Public Sub New(robo As Robo)
        _robo = robo
    End Sub

    Public MustOverride Sub Executar()
    Public MustOverride Sub Desfazer()
End Class
C# VB .NET

Vamos agora criar três comandos que o robô será capaz de executar.

1- MoverCommand - O primeiro comando irá permitir que o robô seja movido para a frente.Se o valor é negativo, o robô se mover para trás.
2-
RotacionarCommand - O segundo comando irá permitir que o robô para ser rotacionado de um número de graus para a esquerda ou para a direita quando o valor for negativo.
3-
EscavarCommand - O terceiro comando move para cima e para baixo a pá de coleta de amostra. Em cada comando, os métodos Executar e Desfazer são implementadas. O método Desfazer realiza a operação inversa do método Executar.

1- MoverCommand


namespace PadraoProjetoCommand
{
    class MoverCommand : RoboCommand
    {
        public int ParaFrente { get; set; }
        public MoverCommand(Robo robo) : base(robo) { }

        public override void Executar()
        {
            _robo.Mover(ParaFrente);
        }

        public override void Desfazer()
        {
            _robo.Mover(-ParaFrente);
        }
    }
}
Class MoverCommand
    Inherits RoboCommand

    Public Property ParaFrente() As Integer
        Get
            Return m_ParaFrente
        End Get
        Set(value As Integer)
            m_ParaFrente = Value
        End Set
    End Property
    Private m_ParaFrente As Integer
    Public Sub New(robo As Robo)
        MyBase.New(robo)
    End Sub

    Public Overrides Sub Executar()
        _robo.Mover(ParaFrente)
    End Sub

    Public Overrides Sub Desfazer()
        _robo.Mover(-ParaFrente)
    End Sub
End Class
C# VB .NET

2 - RotacionarCommand


namespace PadraoProjetoCommand
{
    class RotacionarCommand: RoboCommand
    {
        public double rotacionarParaEsquerda { get; set; }
        public RotacionarCommand(Robo robo) : base(robo) { }
 
        public override void Executar()
        {
            _robo.RotacionarParaEsquerda(rotacionarParaEsquerda);
        }
 
        public override void Desfazer()
        {
            _robo.RotacionarParaEsquerda(-rotacionarParaEsquerda);
        }
    }
}
Class RotacionarCommand
    Inherits RoboCommand

    Public Property rotacionarParaEsquerda() As Double
        Get
            Return m_rotacionarParaEsquerda
        End Get
        Set(value As Double)
            m_rotacionarParaEsquerda = Value
        End Set
    End Property
    Private m_rotacionarParaEsquerda As Double
    Public Sub New(robo As Robo)
        MyBase.New(robo)
    End Sub

    Public Overrides Sub Executar()
        _robo.RotacionarParaEsquerda(rotacionarParaEsquerda)
    End Sub

    Public Overrides Sub Desfazer()
        _robo.RotacionarParaEsquerda(-rotacionarParaEsquerda)
    End Sub
End Class
C# VB .NET

3- EscavarCommand


namespace PadraoProjetoCommand
{
    class EscavarCommand : RoboCommand
    {
        public bool ColherMaterial { get; set; }

        public EscavarCommand(Robo robo) : base(robo) { }
 
        public override void Executar()
        {
            _robo.Escavar(ColherMaterial);
        }
 
        public override void Desfazer()
        {
            _robo.Escavar(!ColherMaterial);
        }
    }
}
Class EscavarCommand
    Inherits RoboCommand
    Public Property ColherMaterial() As Boolean
        Get
            Return m_ColherMaterial
        End Get
        Set(value As Boolean)
            m_ColherMaterial = Value
        End Set
    End Property
    Private m_ColherMaterial As Boolean

    Public Sub New(robo As Robo)
        MyBase.New(robo)
    End Sub

    Public Overrides Sub Executar()
        _robo.Escavar(ColherMaterial)
    End Sub

    Public Overrides Sub Desfazer()
        _robo.Escavar(Not ColherMaterial)
    End Sub
End Class
C# VB .NET

Vamos agora criar a classe Robo que será o Receptor único onde iremos definir as funcionalidades dos comandos. No exemplo estamos apenas emitindo mensagens relacionadas com cada comando.

using System;

namespace PadraoProjetoCommand
{
    class Robo
    {
        public void Mover(int ParaFrente)
        {
            if (ParaFrente > 0)
                Console.WriteLine("O Robo foi movimentado para frente {0}mm.", ParaFrente);
            else
                Console.WriteLine("O Robo foi movimentado para trás {0}mm.", -ParaFrente);
        }

        public void RotacionarParaEsquerda(double rotacionarParaEsquerda)
        {
            if (rotacionarParaEsquerda > 0)
                Console.WriteLine("O Robo foi rotacionaod para esquerda {0} degrees.", rotacionarParaEsquerda);
            else
                Console.WriteLine("O Robo foi rotacionado para direita {0} degrees.", -rotacionarParaEsquerda);
        }

        public void Escavar(bool paraCima)
        {
            if (paraCima)
                Console.WriteLine("O Robo colheu material do solo.");
            else
                Console.WriteLine("O Robo despejou o material colhido.");
        }
    }
}
C#
Class Robo
    Public Sub Mover(ParaFrente As Integer)
        If ParaFrente > 0 Then
            Console.WriteLine("O Robo foi movimentado para frente {0}mm.", ParaFrente)
        Else
            Console.WriteLine("O Robo foi movimentado para trás {0}mm.", -ParaFrente)
        End If
    End Sub

    Public Sub RotacionarParaEsquerda(rotacionarParaEsquerda__1 As Double)
        If rotacionarParaEsquerda__1 > 0 Then
            Console.WriteLine("O Robo foi rotacionaod para esquerda {0} degrees.", rotacionarParaEsquerda__1)
        Else
            Console.WriteLine("O Robo foi rotacionado para direita {0} degrees.", -rotacionarParaEsquerda__1)
        End If
    End Sub

    Public Sub Escavar(paraCima As Boolean)
        If paraCima Then
            Console.WriteLine("O Robo colheu material do solo.")
        Else
            Console.WriteLine("O Robo despejou o material colhido.")
        End If
    End Sub
End Class
VB .NET

Para completar as classes do padrão vamos criar a classe RoboControle que faz o papel de Chamador.

Nesta classe podemos ter uma fila de comandos, em vez de uma única referência. Além disso, uma pilha de comandos será usada para manter um registro das atividades do robô. Esta pilha pode então ser usado para desfazer os comandos que foram executados com erro.


using System;
using System.Collections.Generic;

namespace PadraoProjetoCommand
{
    class RoboControle
    {
        public Queue<RoboCommand> Comandos;
        private Stack<RoboCommand> _desfazerPilha;
 
        public RoboControle()
        {
            Comandos = new Queue<RoboCommand>();
            _desfazerPilha = new Stack<RoboCommand>();
        }
 
        public void ExecutarComandos()
        {
            Console.WriteLine("EXECUTANDO COMANDO(S).");
 
            while (Comandos.Count > 0)
            {
                RoboCommand comando = Comandos.Dequeue();
                comando.Executar();
                _desfazerPilha.Push(comando);
            }
        }
 
        public void DesfazerComandos(int numComandosDesfazer)
        {
            Console.WriteLine("DESFAZENDO {0} COMANDO(S).", numComandosDesfazer);

            while (numComandosDesfazer > 0 && _desfazerPilha.Count > 0)
            {
                RoboCommand comand = _desfazerPilha.Pop();
                comand.Desfazer();
                numComandosDesfazer--;
            }
        }
    }
}
C#
Class RoboControle
	Public Comandos As Queue(Of RoboCommand)
	Private _desfazerPilha As Stack(Of RoboCommand)

	Public Sub New()
		Comandos = New Queue(Of RoboCommand)()
		_desfazerPilha = New Stack(Of RoboCommand)()
	End Sub

	Public Sub ExecutarComandos()
		Console.WriteLine("EXECUTANDO COMANDO(S).")

		While Comandos.Count > 0
			Dim comando As RoboCommand = Comandos.Dequeue()
			comando.Executar()
			_desfazerPilha.Push(comando)
		End While
	End Sub

	Public Sub DesfazerComandos(numComandosDesfazer As Integer)
		Console.WriteLine("DESFAZENDO {0} COMANDO(S).", numComandosDesfazer)

		While numComandosDesfazer > 0 AndAlso _desfazerPilha.Count > 0
			Dim comand As RoboCommand = _desfazerPilha.Pop()
			comand.Desfazer()
			numComandosDesfazer -= 1
		End While
	End Sub
End Class
VB .NET

Vamos agora definir o código que utiliza a implementação do padrão Comando digitando o código a seguir na classe/Modulo Program.cs/Module.vb

using System;
namespace PadraoProjetoCommand
{
    class Program
    {
        static void Main(string[] args)
        {

            Robo robo = new Robo();
            RoboControle controle = new RoboControle();

            MoverCommand mover = new MoverCommand(robo);
            mover.ParaFrente = 1000;
            controle.Comandos.Enqueue(mover);

            RotacionarCommand rotacionar = new RotacionarCommand(robo);
            rotacionar.rotacionarParaEsquerda = 45;
            controle.Comandos.Enqueue(rotacionar);

            EscavarCommand escavar = new EscavarCommand(robo);
            escavar.ColherMaterial = true;
            controle.Comandos.Enqueue(escavar);

            controle.ExecutarComandos();
            controle.DesfazerComandos(3);

            Console.ReadKey();
        }
    }
}
Module Module1

    Sub Main()
        Dim robo As New Robo()
        Dim controle As New RoboControle()

        Dim mover As New MoverCommand(robo)
        mover.ParaFrente = 1000
        controle.Comandos.Enqueue(mover)

        Dim rotacionar As New RotacionarCommand(robo)
        rotacionar.rotacionarParaEsquerda = 45
        controle.Comandos.Enqueue(rotacionar)

        Dim escavar As New EscavarCommand(robo)
        escavar.ColherMaterial = True
        controle.Comandos.Enqueue(escavar)

        controle.ExecutarComandos()
        controle.DesfazerComandos(3)

        Console.ReadKey()
    End Sub

End Module
C# VB .NET

Executando projeto teremos como o seguinte resultado:

O resultado acima mostra a execução dos comandos implementados e a seguir o cancelamento das operações executadas.

Pegue a solução completa aqui: PadraoProjetoCommand.zip PadraoProjetoCommandVB.zip

1Pe 1:13 Portanto, cingindo os lombos do vosso entendimento, sede sóbrios, e esperai inteiramente na graça que se vos oferece na revelação de Jesus Cristo.
1Pe 1:14
Como filhos obedientes, não vos conformeis às concupiscências que antes tínheis na vossa ignorância;
1Pe 1:15
mas, como é santo aquele que vos chamou, sede vós também santos em todo o vosso procedimento;
1Pe 1:16
porquanto está escrito: Sereis santos, porque eu sou santo.

Referências:


José Carlos Macoratti