.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:
|
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:
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:
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.Referências: