Padrão de comando C#

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:

Padrão de design de comando c#

Neste diagrama:

  • ICommand: Define a interface para executar uma operação.
  • ConcreteCommand: implementa a ICommandinterface e define a ligação entre um Receiverobjeto e uma ação. O ConcreteCommandimplementa o método Execute()and Undo()chamando as operações correspondentes no Receiverobjeto.
  • Cliente: Cria ConcreteCommandobjetos 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 Clientcria um ConcreteCommandobjeto e informa qual objeto receptor deve receber o comando.
  • Um Invokerobjeto salva o ConcreteCommandobjeto.
  • O Invokerchama o Executemétodo no ICommandobjeto para realizar a solicitação. Se o comando puder ser desfeito, o ConcreteCommandobjeto armazenará as informações necessárias para desfazer o comando antes de chamar Executeo método.
  • O ConcreteCommandobjeto então direciona o Receiverpara 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 Calculatorclasse 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 Programclasse, criamos uma instância da Calculatorclasse, 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:

15Linguagem 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 ICommandinterface que tenha dois métodos Executee Undo:

public interface ICommand
{
    double Execute(double value);
    double Undo(double value);
}Linguagem de código:  C#  ( cs )

Segundo, defina o AddCommandque implementa a ICommandinterface. Adiciona AddCommandum 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, MultiplyCommande 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 Calculatorclasse 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 Calculatorclasse possui uma CurrentValuepropriedade que representa seu valor atual. Além disso, possui um campo privado _commandHistoryque monitora os comandos executados anteriormente.

O ExecuteCommandmétodo pega um ICommandobjeto. O método chama o Execute()método de um ICommandobjeto com o valor atual da calculadora e então adiciona o ICommandobjeto à pilha do histórico de comandos.

O Undométodo retira o comando mais recente da pilha do histórico de comandos, chama o Undométodo do comando com o valor atual da calculadora e atualiza a CurrentValuepropriedade com o resultado.

Por fim, defina a Programclasse 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
10Linguagem de código:  C#  ( cs )

No Main()método:

  • Primeiro, crie uma nova instância da Calculatorclasse.
  • Em seguida, execute três comandos incluindo AddCommand, SubtractCommande MultiplyCommandusando o ExecuteCommandmétodo do Calculatorobjeto com os argumentos apropriados (20, 10 e 5, respectivamente).
  • Em seguida, exiba o CurrentValueobjeto Calculatorno console.
  • Depois disso, chame o Undométodo do Calculatorobjeto, que desfaz o comando mais recente (neste MultiplyCommandcaso) e atualiza a CurrentValuepropriedade do Calculatorobjeto 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:

  • ICommandinterface: define os métodos comuns que todos os comandos concretos devem implementar.
  • AddCommand, SubtractCommand, MultiplyCommande DivideCommandclasses: comandos concretos que implementam a ICommandinterface e encapsulam as operações que podem ser executadas na CurrentValuepropriedade do Calculatorobjeto.
  • Calculatorclasse: o invocador que recebe e armazena os comandos concretos. Ele executa os comandos chamando seus Executemétodos e os desfaz chamando seus Undométodos.
  • Programclasse: o cliente que cria um Calculatorobjeto e executa os comandos concretos nele. Também invoca o Undométodo do Calculatorobjeto 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.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *