Design Patterns Series 16 - Command Pattern
Today we will discover another pattern from our design patterns list, The Command Pattern
The Command Pattern :
The Command Pattern lets you package complex commands into a single one. Rather than having to preform the multiple steps needed to execute each command every time, you can create a bunch of ready-to-use command objects, each of which can handle multiple steps internally. Each command is already configured with its target and the action it's supposed to perform, so a set of command objects can act as a ready-made toolkit, already configured and all set to operate on the target objects they're supposed to handle.
The Command Pattern says you should encapsulate all the separate actions into objects configured for specific targets. This gives you a number of objects that act like a set of tools, ready to be used.
In other words, the idea here is encapsulation. You are encapsulating a set of complex actions, targeted at a particular target, into an easily handled object. When you want to execute a command, you no longer have to take all the separate steps.You just use the specific prebuilt, preconfigured command object, and it does the work.
You use the Command Pattern to "Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undo-able operations."
In simple terms, the target of a command object is called its receiver. When you create a command object, you typically pass it the receiver it's supposed to work on so it can access that receiver. When you want to execute a command, typically you pass it to an object that acts as an invoker. The invoker calls the methods of the command object or undo action.
You use the Command Pattern when you have got a complex set of commands that get annoying. When the interface to those commands is so complex that it gets in the way, it makes sense to encapsulate those commands.
The inspiration behind this design pattern is that it takes encapsulation to another level. Programmers often use encapsulation to extract a set of methods and data and wrap them in an object to simplify their code. But you don't usually think of encapsulating commands. By preconfiguring those commands to handle the receiver of their actions and bundling complex steps into single, easy-to-handle objects, you end up with a prebuilt toolkit, ready to use. To best understand how we create a Command Pattern and how it works, Suppose our company has three servers in Asia, in Europe, and in USA. We have to monitor these three servers and fix any issue as soon as possible. Assume the commands we run are shutting down the server, run diagnostics tools, and reboot the server. Before running any command we should connect to the server first and after running it we should disconnect.
First thing we will do is, creating the receiver that is what a command object works on. In our example that is one of the three servers. Each server acts as a receiver of the command object's actions. We will create an IReceiver interface which will be implemented by each receiver(each server).
IReciever interface
AsiaServer class
Commands are preconfigured to perform various actions. To make them do their thing, you typically call an Execute() method. In our example we have three commands so we will create a class for each command, ShutdownCommand class, RebootCommand class, and RunDiagnosticsCommand class. Each of these can perform the action corresponding to its name, and each of these can be configured to work with a specific receiver. First we will create a base class contains two methods, Execute() method to execute the command, and Undo() method to undo the command, and IReceiver object that is the receiver will be handled.
Command class
ShutdownCommand class
RunDiagnosticsCommand class
RebootCommand class
The Invoker class starts with a collection of commands to be available when undoing, AddCommand() method for add a new command to the command collection, Run() method that will call the execute method of the command, and Undo() method to call the undo method of the command.
Invoker class
Create the invoker object ,add commands to invoker, and call run method of invoker object or undo method
That is what the Command Pattern is all about - encapsulating commands. As mentioned earlier, this encapsulation is a little different from the usual, where you end up with an object that you can think of as a noun. Here, you think of the resulting object more as a verb. And when you use an invoker, you can handle whole sequences of commands and undo them if needed.
you can download the full source code here
The Command Pattern :
The Command Pattern lets you package complex commands into a single one. Rather than having to preform the multiple steps needed to execute each command every time, you can create a bunch of ready-to-use command objects, each of which can handle multiple steps internally. Each command is already configured with its target and the action it's supposed to perform, so a set of command objects can act as a ready-made toolkit, already configured and all set to operate on the target objects they're supposed to handle.
The Command Pattern says you should encapsulate all the separate actions into objects configured for specific targets. This gives you a number of objects that act like a set of tools, ready to be used.
In other words, the idea here is encapsulation. You are encapsulating a set of complex actions, targeted at a particular target, into an easily handled object. When you want to execute a command, you no longer have to take all the separate steps.You just use the specific prebuilt, preconfigured command object, and it does the work.
You use the Command Pattern to "Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undo-able operations."
In simple terms, the target of a command object is called its receiver. When you create a command object, you typically pass it the receiver it's supposed to work on so it can access that receiver. When you want to execute a command, typically you pass it to an object that acts as an invoker. The invoker calls the methods of the command object or undo action.
You use the Command Pattern when you have got a complex set of commands that get annoying. When the interface to those commands is so complex that it gets in the way, it makes sense to encapsulate those commands.
The inspiration behind this design pattern is that it takes encapsulation to another level. Programmers often use encapsulation to extract a set of methods and data and wrap them in an object to simplify their code. But you don't usually think of encapsulating commands. By preconfiguring those commands to handle the receiver of their actions and bundling complex steps into single, easy-to-handle objects, you end up with a prebuilt toolkit, ready to use. To best understand how we create a Command Pattern and how it works, Suppose our company has three servers in Asia, in Europe, and in USA. We have to monitor these three servers and fix any issue as soon as possible. Assume the commands we run are shutting down the server, run diagnostics tools, and reboot the server. Before running any command we should connect to the server first and after running it we should disconnect.
First thing we will do is, creating the receiver that is what a command object works on. In our example that is one of the three servers. Each server acts as a receiver of the command object's actions. We will create an IReceiver interface which will be implemented by each receiver(each server).
IReciever interface
public interface IReciever
{
void Connect();
void Diagnostics();
void Reboot();
void Shutdown();
void Disconnect();
}
Now we will create three classes for our three receivers, AsiaServer class, EuroServer class, and USAServer class. All classes will implement IReciever interface methods, that for simplicity will print out a message.
AsiaServer class
public class AsiaServer : IReciever
{
public void Connect()
{
Console.WriteLine("You are connect to Asia Server.");
}
public void Diagnostics()
{
Console.WriteLine("The Asia server dignostics check out OK.");
}
public void Reboot()
{
Console.WriteLine("Rebooting The Asia Server.");
}
public void Shutdown()
{
Console.WriteLine("Shutting down The Asia Server.");
}
public void Disconnect()
{
Console.WriteLine("You are disconnected from Asia Server.");
}
}
EuroServer and USAServer classes will be as AsiaServer class with changing the messages that will print correspondingly. you can download the full source from here.Commands are preconfigured to perform various actions. To make them do their thing, you typically call an Execute() method. In our example we have three commands so we will create a class for each command, ShutdownCommand class, RebootCommand class, and RunDiagnosticsCommand class. Each of these can perform the action corresponding to its name, and each of these can be configured to work with a specific receiver. First we will create a base class contains two methods, Execute() method to execute the command, and Undo() method to undo the command, and IReceiver object that is the receiver will be handled.
Command class
public abstract class Command
{
protected IReciever Receiver { get; set; }
public abstract void Execute();
public abstract void Undo();
}
Now will create our commands classes that inherit Command class. We will pass the receiver that it will work on into its constructor.
ShutdownCommand class
public class ShutdownCommand : Command
{
public ShutdownCommand(IReciever receiver)
{
Receiver = receiver;
}
public override void Execute()
{
Receiver.Connect();
Receiver.Shutdown();
Receiver.Disconnect();
}
public override void Undo()
{
Console.WriteLine("Undoing......");
Receiver.Connect();
Receiver.Reboot();
Receiver.Disconnect();
}
}
RunDiagnosticsCommand class
public class RunDiagnosticsCommand : Command
{
public RunDiagnosticsCommand(IReciever receiver)
{
Receiver = receiver;
}
public override void Execute()
{
Receiver.Connect();
Receiver.Diagnostics();
Receiver.Disconnect();
}
public override void Undo()
{
Console.WriteLine("Can't Undo....");
}
}
Note the Undo() Method, it just displays a message because actually we can't undo the diagnostics process just we run it.
RebootCommand class
public class RebootCommand : Command
{
public RebootCommand(IReciever receiver)
{
Receiver = receiver;
}
public override void Execute()
{
Receiver.Connect();
Receiver.Reboot();
Receiver.Disconnect();
}
public override void Undo()
{
Console.WriteLine("Undoing......");
Receiver.Connect();
Receiver.Shutdown();
Receiver.Disconnect();
}
}
That gives you the receivers and the command classes that act on those receivers.
Now we will create the Invoker class that actually puts the commands to work. You typically load a command object into the invoker and tell the invoker to run it. Sometimes you may not need to use the invoker and want to dispense with the invoker altogether and just call the execute method. But the invoker can also keep track of multiple commands in a log or a queue, which make undoing a sequence of commands possible.The Invoker class starts with a collection of commands to be available when undoing, AddCommand() method for add a new command to the command collection, Run() method that will call the execute method of the command, and Undo() method to call the undo method of the command.
Invoker class
public class Invoker
{
private List Commands { get; set; }
private Int32 Position { get; set; }
public Invoker()
{
Commands = new List ();
Position = -1;
}
public void AddCommand(Command command)
{
Commands.Add(command);
Position++;
}
public void Run()
{
Commands[Position].Execute();
}
public void Undo()
{
if (Position >= 0)
{
Commands[Position].Undo();
}
Position--;
}
}
To test our command pattern we need to prepare the receivers objects, servers objects in our example.
AsiaServer asia = new AsiaServer();
EuroServer euro = new EuroServer();
USAServer usa = new USAServer();
Then prepare the commands
ShutdownCommand shutdownasia = new ShutdownCommand(asia);
RunDiagnosticsCommand diagnosticeuro = new RunDiagnosticsCommand(euro);
RebootCommand rebootusa = new RebootCommand(usa);
ShutdownCommand shutdowneuro = new ShutdownCommand(euro);
Note we pass the recevier object to the command that will work on it.Create the invoker object ,add commands to invoker, and call run method of invoker object or undo method
invoker.AddCommand(shutdownasia);
invoker.Run();
Console.WriteLine("***********************************************");
invoker.AddCommand(rebootusa);
invoker.Run();
Console.WriteLine("***********************************************");
invoker.AddCommand(shutdowneuro);
invoker.Run();
Console.WriteLine("***********************************************");
invoker.AddCommand(diagnosticeuro);
invoker.Run();
Note we add a command and call run method directly, because the run method will run the last command we added.The result of these commands will be
You are connect to Asia Server.
Shutting down The Asia Server.
You are disconnected from Asia Server.
***********************************************
You are connect to USA Server.
Rebooting The USA Server.
You are disconnected from USA Server.
***********************************************
You are connect to Euro Server.
Shutting down The Euro Server.
You are disconnected from Euro Server.
***********************************************
You are connect to Euro Server.
The Euro server dignostics check out OK.
You are disconnected from Euro Server.
If we call the undo method of the invoker it will undo the last action, but we will call it twice to undo the last two actions
invoker.Undo();
Console.WriteLine("***********************************************");
invoker.Undo();
And the result will beCan't Undo....
***********************************************
Undoing......
You are connect to Euro Server.
Rebooting The Euro Server.
You are disconnected from Euro Server.
Note the fisrt line of the output that because the last command we run was RunDiagnosticsCommand.
That is what the Command Pattern is all about - encapsulating commands. As mentioned earlier, this encapsulation is a little different from the usual, where you end up with an object that you can think of as a noun. Here, you think of the resulting object more as a verb. And when you use an invoker, you can handle whole sequences of commands and undo them if needed.
you can download the full source code here
Comments
Post a Comment