Sunday 19 December 2010

Command Pattern

"Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations."



The Receiver is the object which ultimately performs the actions.
The Command is an abstract class or interface which enforces implementing classes to implement an Execute() method.
ConcreteCommand is a specialised version of the Command. It implements the Execute() method.
The Invoker is the object that kicks off what will ultimately be the execution of an action by the Receiver.  The Invoker only knows about the Command abstraction, which it is pre-loaded with.  At some later point in time the Invoker is called which creates the following sequence of events:
1. The Invoker calls Execute() on the Command.
2. The call gets routed to the specific ConcreteCommand's implementation of Execute().
3. The ConcreteCommand, in turn, calls the Receiver which carries out the action, which is a public method on the Receiver.
The code example below illustrates the command pattern using the following participants:
The Document class is an example of a Receiver – it is the object which has the actions/methods on it, which are called by the Invoker via a ConcreteCommand object.
DocumentScheduledTask – This is our Invoker and it is ultimately responsible for invoking the actions on the Document Receiver. It does this by calling Execute() on a ConcreteCommand but it neither knows nor cares how the DocumentReceiver interprets the Command as an action.
ICommand – Just the same as the corresponding item on the diagram; it implements Execute() and in addition it also implements Undo().
CopyCommand, SendCommand and BatchCommand are all ConcreteCommand objects.  They are constructed with the Document Receiver that they will delegate the action method to.
Ok, so here's what happens.  The DocumentScheduledTask is loaded with two ConcreteCommand objects - one for copying Documents and one for sending Documents.  Each of these ConcreteCommand objects stores a reference to the underlying Document Receiver.  When the client code calls Commit() on the DocumentScheduledTask Invoker it simply iterates through its list of ICommands and calls Execute() on each one. Although the Invoker doesn't really care, what actually happens is that the DocumentReceiver first copies the Document and then sends it. 
Now, if something were to go wrong, the ConcreteCommand implement an Undo() method which gets called through the DocumentScheduledTask's RollBack() method.  The Undo() methods simply reverse the changes implemented by the Execute() methods, so in the case of the CopyCommand, the Undo() method deletes the copy.
The BatchCommand class illustrates how we can batch together commands into a single  Execute() operation.  In this case the BatchCommand maintains a list of DocumentReceivers and when we call Execute() on it via the DocumentScheduledTask Invoker it simply iterates through them and calls Execute() on each one.  The BatchCommand class also demonstrates a way to implement Undo().  It maintains a stack of Undo() Commands which are pushed onto the stack, so that if we need to call RollBack() on the DocumentScheduledTask we can just pop the methods off the stack and call Invoke() on each one.
This pattern takes a bit of explaining, but the key points I understand from it are that it is really useful for decoupling a request for an object to do some task from the task itself.  It also allows us to batch up commands and present a simple interface for clients to invoke a number of commands in one go.  Finally if we implement Undo() on our Commands we can introduce the concept of transactions and transactional integrity.
using System;
using System.Collections.Generic;
 
namespace Command
{
    class Program
    {
        static void Main(string[] args)
        {
            Document doc = new Document("Example1.doc");
            DocumentScheduledTask dst =
                new DocumentScheduledTask(
                    new CopyCommand(doc),
                    new SendCommand(doc));
      
            dst.Commit();
            dst.RollBack();
 
            List<Document> docs = new List<Document>();
            docs.Add(new Document("Example2A.doc"));
            docs.Add(new Document("Example2B.doc"));
            docs.Add(new Document("Example2C.doc"));
            dst = new DocumentScheduledTask(new BatchCommand(docs));
            dst.Commit();
            dst.RollBack();
 
            Console.ReadLine();
        }
    }
 
    /// <summary>
    /// This is the Invoker class - it is ultimately responsible 
    /// for invoking the actions on the receiver. It doesn't 
    /// actually know (or care) how the receiver implements 
    /// the command.
    /// </summary>
    public class DocumentScheduledTask
    {
        private List<ICommand> _commands =
            new List<ICommand>();
 
        public DocumentScheduledTask(params ICommand[] arg)
        {
            foreach (ICommand cmd in arg)
            {
                _commands.Add(cmd);
            }
        }
 
        public void Commit()
        {
            foreach (ICommand cmd in _commands)
            {
                cmd.Execute();
            }
        }
 
        public void RollBack()
        {
            foreach (ICommand cmd in _commands)
            {
                cmd.Undo();
            }
        } 
    }
 
    /// <summary>
    /// Simple Command interface
    /// </summary>
    public interface ICommand
    {
        void Execute();
        void Undo();
    }
 
    /// <summary>
    /// Command that copies a document and has a delete to 
    /// undo the copy
    /// </summary>
    public class CopyCommand : ICommand
    {
        private Document _original;
        private Document _duplicate;
 
        public CopyCommand(Document doc)
        {
            _original = doc;
        }
 
        public void Execute()
        {
            _duplicate = _original.Copy();
        }
 
        public void Undo()
        {
            _duplicate.Delete();
        }
    }
 
    /// <summary>
    /// Command that sends a document and has a recall
    /// to undo the send
    /// </summary>
    public class SendCommand : ICommand
    {
        private Document _document;
 
        public SendCommand(Document doc)
        {
            _document = doc;
        }
 
        public void Execute()
        {
            _document.Send();
        }
 
        public void Undo()
        {
            _document.Recall();
        }
    }
 
    /// <summary>
    /// Class illustrates how we can use the ICommand interface
    /// to batch up commands. The class maintains a stack of undo 
    /// commands. This illustrates the transactional aspect of 
    /// the command pattern.
    /// </summary>
    public class BatchCommand : ICommand
    {
        private List<Document> _docs =
            new List<Document>();
 
        /// <summary>
        /// We maintain a LIFO stack of undo methods.
        /// </summary>
        delegate void Command();
        private Stack<Command> _commands =
            new Stack<Command>();
  
        public BatchCommand(List<Document> docs)
        {
            _docs = docs;
        }
 
        public void Execute()
        {
            foreach(Document doc in _docs)
            {
                CopyCommand copyCmd = new CopyCommand(doc);
                copyCmd.Execute();
          
                // Add the Undo command to the stack
                _commands.Push(copyCmd.Undo);
 
                SendCommand sendCmd = new SendCommand(doc);
                sendCmd.Execute();
          
                // Add the Undo command to the stack
                _commands.Push(sendCmd.Undo);
            }
        }
 
        public void Undo()
        {
            while (_commands.Count > 0)
            {
                _commands.Pop().Invoke();
            }
        }
    }
 
    /// <summary>
    /// The receiver of the command.
    /// </summary>
    public class Document
    {
        private string Name { getset; }
 
        public Document(string name)
        {
            Name = name;
        }
 
        public Document Copy()
        {
            Console.WriteLine("Copying {0}", Name);
            return new Document(this.Name + ".copy");
        }
 
        public void Delete()
        {
            Console.WriteLine("Deleting {0}", Name);
        }
 
        public void Send()
        {
            Console.WriteLine("Sending {0}", Name);
        }
 
        public void Recall()
        {
            Console.WriteLine("Recalling {0}", Name);
        }
    }
}
Outputs:

Copying Example1.doc
Sending Example1.doc
Deleting Example1.doc.copy
Recalling Example1.doc
Copying Example2A.doc
Sending Example2A.doc
Copying Example2B.doc
Sending Example2B.doc
Copying Example2C.doc
Sending Example2C.doc
Recalling Example2C.doc
Deleting Example2C.doc.copy
Recalling Example2B.doc
Deleting Example2B.doc.copy
Recalling Example2A.doc
Deleting Example2A.doc.copy

No comments:

Post a Comment