© Dmitri Nesteruk 2020
D. NesterukDesign Patterns in .NET Core 3https://doi.org/10.1007/978-1-4842-6180-4_15

15. Command

Dmitri Nesteruk1  
(1)
St. Petersburg, c.St-Petersburg, Russia
 

Think about a trivial variable assignment, such as meaningOfLife = 42. The variable got assigned, but there’s no record anywhere that the assignment took place. Nobody can give us the previous value. We cannot take the fact of assignment and serialize it somewhere. This is problematic, because without a record of the change, we are unable to roll back to previous values, perform audits, or do history-based debugging.1

The Command design pattern proposes that instead of working with objects directly by manipulating them through their APIs, we send them commands: instructions on how to do something. A command is nothing more than a data class with its members describing what to do and how to do it. Let’s take a look at a typical scenario.

Scenario

Let’s try to model a typical bank account that has a balance and an overdraft limit. We’ll implement Deposit() and Withdraw() method on it:
public class BankAccount
{
  private int balance;
  private int overdraftLimit = -500;
  public void Deposit(int amount)
  {
    balance += amount;
    WriteLine($"Deposited ${amount}, balance is now {balance}");
  }
  public void Withdraw(int amount)
  {
    if (balance - amount >= overdraftLimit)
    {
      balance -= amount;
      WriteLine($"Withdrew ${amount}, balance is now {balance}");
    }
  }
  public override string ToString()
  {
    return $"{nameof(balance)}: {balance}";
  }
}

Now we can call the methods directly, of course, but let us suppose that, for audit purposes, we need to make a record of every deposit and withdrawal made and we cannot do it right inside BankAccount because – guess what – we’ve already designed, implemented, and tested that class.2

Implementing the Command Pattern

We’ll begin by defining an interface for a command:
public interface ICommand
{
  void Call();
}
Having made the interface, we can now use it to define a BankAccountCommand that will encapsulate information about what to do with a bank account:
public class BankAccountCommand : ICommand
{
  private BankAccount account;
  public enum Action
  {
    Deposit, Withdraw
  }
    private Action action;
    private int amount;
    public BankAccountCommand
      (BankAccount account, Action action, int amount) { ... }
  }
The information contained in the command includes the following:
  • The account to operate upon.

  • The action to take; both the set of options and the variable to store the action are defined in the class.

  • The amount to deposit or withdraw.

Once the client provides this information, we can take it and use it to perform the deposit or withdrawal:
public void Call()
{
  switch (action)
  {
    case Action.Deposit
      account.Deposit(amount);
      succeeded = true;
      break;
    case Action.Withdraw:
      succeeded = account.Withdraw(amount);
      break;
    default:
      throw new ArgumentOutOfRangeException();
  }
}
With this approach, we can create the command and then perform modifications of the account right on the command:
var ba = new BankAccount();
var cmd = new BankAccountCommand(ba,
  BankAccountCommand.Action.Deposit, 100);
cmd.Call(); // Deposited $100, balance is now 100
WriteLine(ba); // balance: 100

This will deposit 100 dollars into our account. Easy! And if you’re worried that we’re still exposing the original Deposit() and Withdraw() member functions to the client, well, the only way to hide them is to make commands inner classes of the BankAccount itself .

Undo Operations

Since a command encapsulates all information about some modification to a BankAccount, it can equally roll back this modification and return its target object to its prior state.

To begin with, we need to decide whether to stick undo-related operations into our Command interface. I will do it here for purposes of brevity, but in general, this is a design decision that needs to respect the Interface Segregation Principle that we discussed at the beginning of the book. For example, if you envisage some commands being final and not subject to undo mechanics, it might make sense to split ICommand into, say, ICallable and IUndoable.

Anyways, here’s the updated ICommand:
public interface ICommand
{
  void Call();
  void Undo();
}
And here is a naïve (but working) implementation of BankAccountCommand.Undo(), motivated by the (incorrect) assumption that Deposit() and Withdraw() are symmetric operations:
public void Undo()
{
  switch (action)
  {
    case Action.Deposit:
      account.Withdraw(amount);
      break;
    case Action.Withdraw:
      account.Deposit(amount);
      break;
    default:
      throw new ArgumentOutOfRangeException();
  }
}

Why is this implementation broken? Because if you tried to withdraw an amount equal to the GDP of a developed nation, you would not be successful, but when rolling back the transaction, we don’t have a way of telling that it failed!

To get this information, we modify Withdraw() to return a success flag:
public bool Withdraw(int amount)
{
  if (balance - amount >= overdraftLimit)
  {
    balance -= amount;
    Console.WriteLine($"Withdrew ${amount}, balance is now {balance}");
    return true; // succeeded
  }
  return false; // failed
}
That’s much better! We can now modify the entire BankAccountCommand to do two things:
  • Store internally a succeeded flag when a withdrawal is made. We assume that Deposit() cannot fail.

  • Use this flag when Undo() is called.

Here we go:
public class BankAccountCommand : ICommand
{
  ...
  private bool succeeded;
}
Okay, so now we have the flag, we can improve our implementation of Undo():
public void Undo()
{
  if (!succeeded) return;
  switch (action)
  {
    case Action.Deposit:
      account.Deposit(amount); // assumed to always succeed
      succeeded = true;
      break;
    case Action.Withdraw:
      succeeded = account.Withdraw(amount);
      break;
    default:
      throw new ArgumentOutOfRangeException();
  }
}
Tada! We can finally undo withdrawal commands in a consistent fashion.
var ba = new BankAccount();
var cmdDeposit = new BankAccountCommand(ba,
  BankAccountCommand.Action.Deposit, 100);
var cmdWithdraw = new BankAccountCommand(ba,
  BankAccountCommand.Action.Withdraw, 1000);
cmdDeposit.Call();
cmdWithdraw.Call();
WriteLine(ba); // balance: 100
cmdWithdraw.Undo();
cmdDeposit.Undo();
WriteLine(ba); // balance: 0

The goal of this exercise was, of course, to illustrate that in addition to storing information about the action to perform, a Command can also store some intermediate information that is, once again, useful for things like audits. If you detect a series of 100 failed withdrawal attempts, you can investigate a potential hack.

Composite Commands (a.k.a. Macros)

A transfer of money from account A to account B can be simulated with two commands :
  1. 1.

    Withdraw $X from A.

     
  2. 2.

    Deposit $X to B.

     

It would be nice if instead of creating and calling these two commands, we could just create and call a single command that encapsulates both of them. This is the essence of the Composite design pattern that we’ll discuss later.

Let’s define a skeleton composite command. I’m going to inherit from List<BankAccountCommand> and, of course, implement the ICommand interface:
abstract class CompositeBankAccountCommand
  : List<BankAccountCommand>, ICommand
{
  public virtual void Call()
  {
    ForEach(cmd => cmd.Call());
  }
  public virtual void Undo()
  {
    foreach (var cmd in
      ((IEnumerable<BankAccountCommand>)this).Reverse())
    {
      cmd.Undo();
    }
  }
}

As you can see, the CompositeBankAccountCommand is both a list and a Command, which fits the definition of the Composite design pattern. I’ve implemented both Undo() and Redo() operations ; note that the Undo() process goes through commands in reverse order; hopefully, I don’t have to explain why you’d want this as default behavior. The cast is there because a List<T> has its own, void-returning, mutating Reverse() that we definitely do not want. If you don’t like what you see here, you can use a for loop or some other base type that doesn’t do in-place reversal .

So now, how about a composite command specifically for transferring money? I would define it as follows:
class MoneyTransferCommand : CompositeBankAccountCommand
{
  public MoneyTransferCommand(BankAccount from,
    BankAccount to, int amount)
  {
    AddRange(new []
    {
      new BankAccountCommand(from,
        BankAccountCommand.Action.Withdraw, amount),
      new BankAccountCommand(to,
        BankAccountCommand.Action.Deposit, amount)
    });
  }
}

As you can see, all we’re doing is providing a constructor to initialize the object with. We keep reusing the base class Undo() and Redo() implementations.

../images/476082_2_En_15_Chapter/476082_2_En_15_Figa_HTML.jpg

But wait, that’s not right, is it? The base class implementations don’t quite cut it because they don’t incorporate the idea of failure. If I fail to withdraw money from A, I shouldn’t deposit that money to B: the entire chain should cancel itself .

To support this idea, more drastic changes are required. We need to
  • Add a Success flag to Command. This of course implies that we can no longer use an interface – we need an abstract class.

  • Record the success or failure of every operation.

  • Ensure that the command can only be undone if it originally succeeded.

  • Introduce a new in-between class called DependentCompositeCommand that is very careful about actually rolling back the commands.

Let’s assume that we’ve performed the refactoring such that Command is now an abstract class with a Boolean Success member; the BankAccountCommand now overrides both Undo() and Redo().

When calling each command, we only do so if the previous one succeeded; otherwise, we simply set the success flag to false.
public override void Call()
{
  bool ok = true;
  foreach (var cmd in this)
  {
    if (ok)
    {
      cmd.Call();
      ok = cmd.Success;
    }
    else
    {
      cmd.Success = false;
    }
  }
}
There is no need to override the Undo() because each of our commands checks its own Success flag and undoes the operation only if it’s set to true. Here’s a scenario that demonstrates the correct operation of the new scheme when the source account doesn’t have enough funds for the transfer to succeed.
var from = new BankAccount();
from.Deposit(100);
var to = new BankAccount();
var mtc = new MoneyTransferCommand(from, to, 1000);
mtc.Call();
WriteLine(from); // balance: 100
WriteLine(to); // balance: 0

One can imagine an even stronger form of the preceding code where a composite command only succeeds if all of its parts succeed (think about a transfer where the withdrawal succeeds but the deposit fails because the account is locked – would you want it to go through?) – this is a bit harder to implement, and I leave it as an exercise for the reader.

The entire purpose of this section was to illustrate how a simple Command-based approach can get quite complicated when real-world business requirements are taken into account. Whether or not you actually need this complexity… well, that is up to you .

Functional Command

The Command design pattern is typically implemented using classes. It is, however, possible to also implement this pattern in a functional way.

First of all, one might argue that an ICommand interface with a single Call() method is simply unnecessary: we already have delegates such as Func and Action that can serve as de facto interfaces for our purposes. Similarly, when it comes to invoking the commands, we can invoke said delegates directly instead of calling a member of some interface.

Here’s a trivial illustration of the approach. We begin by defining a BankAccount simply as
public class BankAccount
{
  public int Balance;
}
We can then define different commands to operate on the bank account as independent methods. These could, alternatively, be packaged into ready-made function objects – there’s no real difference between the two:
public void Deposit(BankAccount account, int amount)
{
  account.Balance += amount;
}
public void Withdraw(BankAccount account, int amount)
{
  if (account.Balance >= amount)
    account.Balance -= amount;
}
Every single method represents a command. We can therefore bundle up the commands in a simple list and process them one after another:
var ba = new BankAccount();
var commands = new List<Action>();
commands.Add(() => Deposit(ba, 100));
commands.Add(() => Withdraw(ba, 100));
commands.ForEach(c => c());
You may feel that this model is a great simplification of the one we had previously when talking about ICommand. After all, any invocation can be reduced to a parameterless Action that simply captures the needed elements in the lambda. However, this approach has significant downsides , namely:
  • Direct references: A lambda that captures a specific object by necessity extends its lifetime. While this is great in terms of correctness (you’ll never invoke a command with a nonexistent object), there are situations where you want commands to persist longer than the objects they need to affect.

  • Logging: If you wanted to record every single action being performed on an account, you still need some sort of command processor. But how can you determine which command is being invoked? All you’re looking at is an Action or similarly nondescript delegate; how do you determine whether it’s a deposit or a withdrawal or something entirely different, like a composite command?

  • Marshaling: Quite simply, you cannot marshal a lambda. You could maybe marshal an expression tree (as in, an Expression<Func<>>), but even then, parsing expression trees is not the easiest of things. A conventional OOP-based approach is easier because a class can be deterministically (de)serialized.

  • Secondary operations: Unlike functional objects, an OOP command (or its interface) can define operations other than invocation. We’ve looked at examples such as Undo(), but other operations could include things like Log(), Print(), or something else. A functional approach doesn’t give you this sort of flexibility.

To sum up, while the functional pattern does represent some action that needs to be done, it only encapsulates its principal behavior. A function is difficult to inspect/traverse, it is difficult to serialize, and if it captures context , this has obvious lifetime implications. Use with caution!

Queries and Command-Query Separation

The notion of Command-Query Separation (CQS) is the idea that operations in a system fall broadly into the following two categories:
  • Commands, which are instructions for the system to perform some operation that involves mutation of state, but yields no value

  • Queries, which are requests for information that yield values but do not mutate state

The GoF book does not define a Query as a separate pattern, so in order to settle this issue once and for all, I propose the following, very simple, definition:

A query is a special type of command that does not mutate state. Instead, a query instructs components to provide some information, such as a value calculated on the basis of interaction with one or more components.

There. We can now argue that both parts of CQS fall under the Command design pattern, the only difference being that queries have a return value – not in the return sense, of course, but rather in having a mutable field/property that any command processor can initialize or modify.

Summary

The Command design pattern is simple: what it basically suggests is that components can communicate with one another using special objects that encapsulate instructions, rather than specifying those same instructions as arguments to a method.

Sometimes, you don’t want such an object to mutate the target or cause it to do something specific; instead you want to use such an object to get some info from the target, in which case we typically call such an object a Query. While, in most cases, a query is an immutable object that relies on the return type of the method, there are situations (see, e.g., the Chain of Responsibility “Broker Chain” example) when you want the result that’s being returned to be modified by other components. But the components themselves are still not modified, only the result is.

Commands are used a lot in UI systems to encapsulate typical actions (e.g., copy or paste) and then allow a single command to be invoked by several different means. For example, you can Copy by using the top-level application menu, a button on the toolbar, the context menu, or by pressing a keyboard shortcut.

Finally, these actions can be combined into composite commands (macros) – sequences of actions that can be recorded and then replayed at will. Notice that a composite command can also be composed of other composite command (as per the Composite design pattern).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.136.18.48