Partager via


Reversible Command Pattern

The Reversible Command design pattern encapsulates a request or an operation as an object, thereby allowing you to queue, log, and serialize requests to a store (volatile or durable) and create undo or rollback requests.

With this pattern you basically log all the requests and execute them through a command invoker entity.

Why is this important?

Most transactional resource managers are capable of logging user requests during transaction. When transaction is being committed, commands are executed on the resource. Because the commands had been captured during transaction, it is possible to examine those commands and create undo requests. Inside the rollback process, those undo commands can then be executed.

Where else can I use this?

In scenarios that you need to capture commands and replay those back. You have the ability to serialize the commands (they are simply .NET classes which can be serializable) and replay those at a later date.

Structure

At the heart of this pattern is an interface that defines a command. This interface exposes an Execute method which is called to execute the logged command. It also provides a ReverseCommand property which is a reference to an ICommand, capable of undoing the request.

As you can see, it is possible create a concrete command by implementing ICommand.

There is one more required entity here to queue all commands and to execute them. This entity is usually called CommandInvoker and is responsible for the following:

- Keep a list of all commands

- Execute all commands

- Keep track of all successfully executed commands

- Allow the user to revert back all successfully executed commands by executing their reverse commands

Sample Implementation

Here is a sample implementation of this pattern:

ICommand

public interface ICommand

{

  void Execute();

  ICommand ReverseCommand

  {

    get;

  }

}

CommandInvoker

public class CommandInvoker

{

 SynchronizedCollection<ICommand> _commands =

   new SynchronizedCollection<ICommand>();

 Queue<ICommand> _executedCommands;

 /// <summary>

 /// Call this method to add a new command to the

 /// list of in-memory commands

 /// </summary>

 public void AddCommand(ICommand command)

 {

  // add commands and get them ready for execution

  _commands.Add(command);

 }

 /// <summary>

 /// Execute all the in-memory commands

 /// </summary>

 public void Execute()

 {

  // perform a double-check-lock

  if (_commands.Count > 0)

  {

   lock (_commands.SyncRoot)

   {

    if (_commands.Count > 0)

    {

     _executedCommands = new Queue<ICommand>();

     // now execute all the

     // commands one at a time...

     foreach (ICommand command in _commands)

     {

      try

      {

       command.Execute();

       // keep track of all the

       // successfully executed commands

       ICommand rc = command.ReverseCommand;

       if (rc != null)

       {

        _executedCommands.Enqueue(rc);

       }

      }

      catch (Exception ex)

      {

       Debug.WriteLine(

         string.Format(

         "Exception = {0}\r\nCommand = {1}",

         ex.Message, command.ToString()));

       throw;

      }

     }

    }

   }

  }

 }

 /// <summary>

 /// Revert back all the successfully

 /// executed commands

 /// </summary>

 public void RevertExecutions()

 {

  if (_executedCommands != null &&

   _executedCommands.Count > 0)

  {

   lock (_commands.SyncRoot)

   {

    while (_executedCommands.Count > 0)

    {

     _executedCommands.Dequeue().Execute();

    }

   }

  }

 }

}

Concrete Commands

The next step is to build a few concrete commands. For this example I will create 2 simple commands (one reversing the other):

AddUser Command

public class AddUser : ICommand

{

 private IUserManager _receiver;

 public string Username { get; set; }

 public AddUser(

  IUserManager receiver, string username)

 {

  _receiver = receiver;

  Username = username;

 }

 public void Execute()

 {

  _receiver.AddUser(Username);

 }

 public ICommand ReverseCommand

 {

  get

  {

   return new RemoveUser(_receiver, Username);

  }

 }

}

RemoveUser Command

This command will look identical to the AddCommand. Its ReverseCommand property will return an instance of the AddCommand (the reverse of Remove is Add).

As you can see both commands have access to a receiver. This is a reference to an object that the command will be executed on.

Finally,

Everything is in place; I just need to write some code to test it:

// create a user manager

UserManager userManager = new UserManager();

// create a command invoker

CommandInvoker invoker = new CommandInvoker();

// let's log a number of requests

invoker.AddCommand(

 new AddUser(userManager, "Pedram"));

invoker.AddCommand(

    new AddUser(userManager, "Sara"));

invoker.AddCommand(

    new AddUser(userManager, "James"));

// display all users before the execution of all commands

Console.WriteLine("Before execution of commands");

Console.WriteLine(userManager);

// now lets execute all the commands

invoker.Execute();

// display all users after the execution of all commands

Console.WriteLine("After execution of commands");

Console.WriteLine(userManager);

// revert back the commands

invoker.RevertExecutions();

// display all users after the revert operation

Console.WriteLine("After revert execution of commands");

Console.WriteLine(userManager);

Console.ReadLine();

When the above code is executed, you’ll notice that although new commands are created, they are not actually executed until the Execute method on the Command Invoker is called. It is also possible to rollback the changes by calling RevertExecutions:

You can download the samples code from here (It is a Visual Studio 2008 beta 2 solution).

CommandPattern.zip

Comments

  • Anonymous
    October 02, 2007
    PingBack from http://www.artofbam.com/wordpress/?p=4613

  • Anonymous
    October 26, 2008
    The comment has been removed

  • Anonymous
    May 20, 2009
    Hi Pedram, I made a command pattern addition which I called command transaction (http://code-schmoof.com/2009/05/17/command-transactions/). With a transaction you can perform a set of commands, and if you need to cancel the set of commands (e.g. the user presses ESC), you can simply rollback. If you're interessted, check it out. Cheers, Nico