Resumo : neste tutorial, você aprenderá sobre o padrão de design C# Command e como usá-lo para tornar seu aplicativo mais flexível e extensível.
Introdução ao padrão de design de comando C#
O padrão Command é um padrão de design comportamental que encapsula uma solicitação como um objeto, permitindo assim que o solicitante seja desacoplado do objeto que opera.
O padrão Command encapsula a solicitação como um objeto de comando que contém todas as informações necessárias para executar a solicitação.
O solicitante não precisa saber os detalhes de como o comando é executado. Em vez disso, ele simplesmente solicita a execução do comando chamando seu método execute.
Isso torna seu aplicativo mais flexível e extensível porque o solicitante pode alternar facilmente comandos diferentes sem a necessidade de entender como esses comandos são executados.
Como o objeto de comando armazena as informações necessárias para reverter ou reproduzir a operação, o padrão Command permite recursos como desfazer e refazer.
Estrutura do padrão de comando C#
O diagrama UML a seguir ilustra o padrão de design Command:
Neste diagrama:
ICommand
: Define a interface para executar uma operação.ConcreteCommand
: implementa aICommand
interface e define a ligação entre umReceiver
objeto e uma ação. OConcreteCommand
implementa o métodoExecute()
andUndo()
chamando as operações correspondentes noReceiver
objeto.- Cliente: Cria
ConcreteCommand
objetos e configura seus receptores. - Receptor: Sabe como realizar as operações associadas à execução de uma solicitação.
- Invoker: Solicita ao comando para realizar a solicitação.
Colaboração entre participantes:
- O
Client
cria umConcreteCommand
objeto e informa qual objeto receptor deve receber o comando. - Um
Invoker
objeto salva oConcreteCommand
objeto. - O
Invoker
chama oExecute
método noICommand
objeto para realizar a solicitação. Se o comando puder ser desfeito, oConcreteCommand
objeto armazenará as informações necessárias para desfazer o comando antes de chamarExecute
o método. - O
ConcreteCommand
objeto então direciona oReceiver
para executar as ações necessárias para atender à solicitação.
A seguir mostramos como implementar o padrão de comando em C# (sem Undo()
método, mais sobre isso no próximo exemplo):
namespace CommandDesignPattern;
public interface ICommand
{
void Execute();
}
public class Receiver
{
public void Action()
{
Console.WriteLine("Perform an action");
}
}
public class ConcreteCommand : ICommand
{
private Receiver Receiver
{
get; set;
}
public ConcreteCommand(Receiver receiver)
{
Receiver = receiver;
}
public void Execute() => Receiver.Action();
}
public class Invoker
{
public ICommand? Command
{
get; set;
}
public void Invoke() => Command?.Execute();
}
class Client
{
public static void Main(string[] args)
{
var receiver = new Receiver();
var concreteCommand = new ConcreteCommand(receiver);
var invoker = new Invoker
{
Command = concreteCommand
};
invoker.Invoke();
}
}
Linguagem de código: C# ( cs )
Exemplo de padrão de comando C#
O programa a seguir define uma Calculator
classe com métodos para operações aritméticas básicas, incluindo adição, subtração, multiplicação e divisão, que atualizam o valor atual.
No Main()
método da Program
classe, criamos uma instância da Calculator
classe, adicionamos 20 ao valor atual, subtraímos 5 dele e, a seguir, exibimos o valor atual da calculadora no console:
namespace CommandPattern;
public class Calculator
{
public double CurrentValue
{
get; private set;
}
public double Add(double valueToAdd) => CurrentValue += valueToAdd;
public double Subtract(double valueToSubtract) => CurrentValue -= valueToSubtract;
public double Divide(double valueToDivide) => CurrentValue /= valueToDivide;
public double Mutiply(double valueToMultiply) => CurrentValue *= valueToMultiply;
}
public class Program
{
public static void Run(string[] args)
{
var calculator = new Calculator();
calculator.Add(20);
calculator.Subtract(5);
Console.WriteLine(calculator.CurrentValue);
}
}
Linguagem de código: C# ( cs )
Saída:
15
Linguagem de código: C# ( cs )
O programa atualmente não oferece suporte ao recurso de desfazer porque não está usando o padrão Command. Para adicionar o recurso de desfazer, podemos refatorar o programa aplicando o padrão Command:
namespace CommandPattern;
public interface ICommand
{
double Execute(double value);
double Undo(double value);
}
public class AddCommand : ICommand
{
private readonly double _valueToAdd;
public AddCommand(double valueToAdd)
{
_valueToAdd = valueToAdd;
}
public double Execute(double currentValue) => currentValue += _valueToAdd;
public double Undo(double currentValue) => currentValue -= _valueToAdd;
}
public class SubtractCommand : ICommand
{
private readonly double _valueToSubtract;
public SubtractCommand(double valueToSubtract)
{
_valueToSubtract = valueToSubtract;
}
public double Execute(double currentValue) => currentValue -= _valueToSubtract;
public double Undo(double currentValue) => currentValue += _valueToSubtract;
}
public class DivideCommand : ICommand
{
private readonly double _valueToDivide;
public DivideCommand(double valueToDivide)
{
_valueToDivide = valueToDivide;
}
public double Execute(double currentValue) => currentValue /= _valueToDivide;
public double Undo(double currentValue) => currentValue *= _valueToDivide;
}
public class MultiplyCommand : ICommand
{
private readonly double _valueToMultiply;
public MultiplyCommand(double valueToMultiply)
{
_valueToMultiply = valueToMultiply;
}
public double Execute(double currentValue) => currentValue *= _valueToMultiply;
public double Undo(double currentValue) => currentValue /= _valueToMultiply;
}
public class Calculator
{
public double CurrentValue
{
get; private set;
}
public Stack<ICommand> _commandHistory = new();
public void ExecuteCommand(ICommand command)
{
CurrentValue = command.Execute(CurrentValue);
_commandHistory.Push(command);
}
public void Undo()
{
var command = _commandHistory.Pop();
CurrentValue = command.Undo(CurrentValue);
}
}
public class Program
{
public static void Main(string[] args)
{
var calculator = new Calculator();
calculator.ExecuteCommand(new AddCommand(20));
calculator.ExecuteCommand(new SubtractCommand(10));
calculator.ExecuteCommand(new MultiplyCommand(5));
Console.WriteLine(calculator.CurrentValue);
calculator.Undo();
Console.WriteLine(calculator.CurrentValue);
}
}
Linguagem de código: C# ( cs )
Como funciona.
Primeiro, crie uma ICommand
interface que tenha dois métodos Execute
e Undo
:
public interface ICommand
{
double Execute(double value);
double Undo(double value);
}
Linguagem de código: C# ( cs )
Segundo, defina o AddCommand
que implementa a ICommand
interface. Adiciona AddCommand
um valor a um valor atual:
public class AddCommand : ICommand
{
private readonly double _valueToAdd;
public AddCommand(double valueToAdd)
{
_valueToAdd = valueToAdd;
}
public double Execute(double currentValue) => currentValue += _valueToAdd;
public double Undo(double currentValue) => currentValue -= _valueToAdd;
}
Linguagem de código: C# ( cs )
O Execute()
método adiciona um valor ao valor atual. E o Undo()
método subtrai o mesmo valor do valor atual.
Da mesma forma, defina as classes SubtractCommand
, MultiplyCommand
e DivideCommand
:
public class SubtractCommand : ICommand
{
private readonly double _valueToSubtract;
public SubtractCommand(double valueToSubtract)
{
_valueToSubtract = valueToSubtract;
}
public double Execute(double currentValue) => currentValue -= _valueToSubtract;
public double Undo(double currentValue) => currentValue += _valueToSubtract;
}
public class DivideCommand : ICommand
{
private readonly double _valueToDivide;
public DivideCommand(double valueToDivide)
{
_valueToDivide = valueToDivide;
}
public double Execute(double currentValue) => currentValue /= _valueToDivide;
public double Undo(double currentValue) => currentValue *= _valueToDivide;
}
public class MultiplyCommand : ICommand
{
private readonly double _valueToMultiply;
public MultiplyCommand(double valueToMultiply)
{
_valueToMultiply = valueToMultiply;
}
public double Execute(double currentValue) => currentValue *= _valueToMultiply;
public double Undo(double currentValue) => currentValue /= _valueToMultiply;
}
Linguagem de código: C# ( cs )
Terceiro, defina a Calculator
classe que executa um comando e o desfaz:
public class Calculator
{
public double CurrentValue
{
get; private set;
}
public Stack<ICommand> _commandHistory = new();
public void ExecuteCommand(ICommand command)
{
CurrentValue = command.Execute(CurrentValue);
_commandHistory.Push(command);
}
public void Undo()
{
var command = _commandHistory.Pop();
CurrentValue = command.Undo(CurrentValue);
}
}
Linguagem de código: C# ( cs )
A Calculator
classe possui uma CurrentValue
propriedade que representa seu valor atual. Além disso, possui um campo privado _commandHistory
que monitora os comandos executados anteriormente.
O ExecuteCommand
método pega um
objeto. O método chama o ICommand
Execute()
método de um
objeto com o valor atual da calculadora e então adiciona o ICommand
objeto à pilha do histórico de comandos.ICommand
O Undo
método retira o comando mais recente da pilha do histórico de comandos, chama o Undo
método do comando com o valor atual da calculadora e atualiza a CurrentValue
propriedade com o resultado.
Por fim, defina a Program
classe que demonstra a capacidade de executar e desfazer comandos usando o padrão Command:
public class Program
{
public static void Main(string[] args)
{
var calculator = new Calculator();
calculator.ExecuteCommand(new AddCommand(20)); // -> 20
calculator.ExecuteCommand(new SubtractCommand(10)); // -> 10
calculator.ExecuteCommand(new MultiplyCommand(5)); // -> 50
Console.WriteLine(calculator.CurrentValue); // output 50
calculator.Undo(); // 10
Console.WriteLine(calculator.CurrentValue); // output 10
}
}
Linguagem de código: C# ( cs )
Saída:
50
10
Linguagem de código: C# ( cs )
No Main()
método:
- Primeiro, crie uma nova instância da
Calculator
classe. - Em seguida, execute três comandos incluindo
AddCommand
,SubtractCommand
eMultiplyCommand
usando oExecuteCommand
método doCalculator
objeto com os argumentos apropriados (20, 10 e 5, respectivamente). - Em seguida, exiba o
CurrentValue
objetoCalculator
no console. - Depois disso, chame o
Undo
método doCalculator
objeto, que desfaz o comando mais recente (nesteMultiplyCommand
caso) e atualiza aCurrentValue
propriedade doCalculator
objeto com o resultado da operação de desfazer. - Por fim, imprima o valor atual atualizado da calculadora no console.
Neste programa, os seguintes objetos são mapeados para o padrão Command:
ICommand
interface: define os métodos comuns que todos os comandos concretos devem implementar.AddCommand
,SubtractCommand
,MultiplyCommand
eDivideCommand
classes: comandos concretos que implementam aICommand
interface e encapsulam as operações que podem ser executadas naCurrentValue
propriedade doCalculator
objeto.Calculator
classe: o invocador que recebe e armazena os comandos concretos. Ele executa os comandos chamando seusExecute
métodos e os desfaz chamando seusUndo
métodos.Program
classe: o cliente que cria umCalculator
objeto e executa os comandos concretos nele. Também invoca oUndo
método doCalculator
objeto para desfazer o último comando executado.
Resumo
- Use o padrão de comando para desacoplar o solicitante e o receptor de uma solicitação, encapsulando a solicitação como um objeto.