My new video on the command design pattern is online at:
Introduction
Any object oriented software operates on interaction of various software components called objects. A typical interaction between two objects is a “client-server” interaction where one object sends a request and the other object processes the request and returns a response. The command design pattern provides a way for the client to encapsulate each request as an object (command) and submit each such request object to a another object called the invoker.
Key Features of the Command Design Pattern:
- All the invoker knows is that it holds a collection of commands and it can execute those commands immediately or later. Also, the invoker does not know the details of what each command does.
- Since all commands implement a common interface, the invoker can execute those via the common interface and that way its not coupled to any concrete command and the details of what each command does.
- Another key player in this type of interaction is the receiver object. Each command encapsulates its receiver object. And there can be more than one receiver involved in the command pattern object interaction if necessary.
- A command is initiated by the invoker object but it is the receiver object that eventually processes the command.
- A command object is self-contained – it knows its receiver and all the data that is needed to execute that command. So a command can be persisted in a “queue” and executed later.
So one advantage of using command pattern is that creation of commands is independent of when it is executed. Also, the commands can be grouped in multiple groups and executed in a batch like a macro. So if we were implementing an airplane’s navigation system, all the commands for a plane to depart (taxi to runway, takeoff, adjust altitude and speed to go up to 10000 feet) and arrive (reduce speed and altitude to be able to land, land and taxi to gate) can be two sets of commands. Each set is basically like a macro and can be executed at different points of time by the invoker which is the cockpit dashboard. Another advantage of the command design pattern is that the commands can be executed as a transaction where either all commands are executed or none of these are executed. In such as case, the command interface could provide two stubs – execute and undo. The ‘undo’ operation would reverse all the actions of a command. So if the invoker encounters an error, it would execute undo operation on all commands executed to that point and this will essentially be an “all or nothing” transnational operation.
The video link at the beginning of this post includes all the details of the command design pattern with an implementation of a logging framework. The source code is provided below.
Abstract Command:
//common contract for all logger commands //this is the abstract command public interface ILogMessageCommand { void LogMessage(); } |
Concrete Command for File Logging:
//concrete command public class LogMessagetoFileCommand: ILogMessageCommand { //receiver FileLog _fileLog = null; //the message to be logged String _message = null; public LogMessagetoFileCommand(FileLog filelog, String message) { _fileLog = filelog; _message = message; } public void LogMessage() { //interact with the receiver to process the command _fileLog.LogMessage(_message); } } |
Concrete Command for Database Logging:
public class LogMessagetoDatabaseCommand : ILogMessageCommand { DatabaseLog _dbLog = null; //receiver String _message = null; //the message to be logged public LogMessagetoDatabaseCommand(DatabaseLog dblog, String message) { _dbLog = dblog; _message = message; } public void LogMessage() { //interact with the receiver to process the command _dbLog.LogMessage(_message); } } |
FileLog Receiver:
public class FileLog { //singleton instance public static readonly FileLog Instance = new FileLog(); //constructor should be private for singleton implementation private FileLog() { } //read the log file path from the application configuration file String _filePath = ConfigurationManager.AppSettings["LogFilePath"].ToString(); //write the log message to the file public void LogMessage(String message) { Console.WriteLine("Writing to a log file"); string[] AllMessages = new string[] {message}; File.WriteAllLines(_filePath, AllMessages); } } |
Database Log Receiver:
public class DatabaseLog { //singleton instance public static readonly DatabaseLog Instance = new DatabaseLog(); //constructor should be private for singleton implementation private DatabaseLog() { } //read the connection string from the application configuration file String _connectionString = ConfigurationManager.ConnectionStrings["LogDatabase"].ToString(); //log the message to a database table //not implemented yet public void LogMessage(String message) { Console.WriteLine("Writing to a logging database"); try { IDbConnection Conn = new SqlConnection(_connectionString); IDbCommand Cmd = new SqlCommand(); Cmd.CommandType = CommandType.StoredProcedure; //stored procedure name for logging Cmd.CommandText = "usp_AddText"; Cmd.Parameters.Add(new SqlParameter("@message", message)); //Conn.Open(); //Cmd.ExecuteNonQuery(); not implemented //Conn.Close(); } catch (Exception ex) { //TODO: handle exception } } } |
Invoker:
public class LoggerFramework { List _allCommands = null; //receive all commands public LoggerFramework(List allCommands) { _allCommands = allCommands; } //invoker them one by one //does not know concrete commands public void LogMessages() { foreach (ILogMessageCommand command in _allCommands) { command.LogMessage(); } } } |
Invoker:
public class LoggerFramework { List _allCommands = null;</code> //receive all commands public LoggerFramework(List allCommands) { _allCommands = allCommands; } //invoker them one by one //does not know concrete commands public void LogMessages() { foreach (ILogMessageCommand command in _allCommands) { command.LogMessage(); } } } |
Client:
//create concrete command objects //create log message to file command //and set the receiver and the message on it ILogMessageCommand LogMessageToFileCommand = new LogMessagetoFileCommand(FileLog.Instance, "Message for file"); //create log message to database command and //set the receiver and the message on it ILogMessageCommand LogMessagetoDbCommand = new LogMessagetoDatabaseCommand(DatabaseLog.Instance, "Message for database"); //create a list of commands List _allCommands = new List(); _allCommands.Add(LogMessageToFileCommand); _allCommands.Add(LogMessagetoDbCommand); //create the invoker and set the commands on it LoggerFramework framework = new LoggerFramework(_allCommands); //request the invoker to execute all commands framework.LogMessages(); //display a message for the user so thay can quit the program Console.WriteLine("Press any key to quit."); //wait for user input before console window is closed Console.ReadKey(); |
Sorry this turned out to be a long post partly due to the inclusion of the source code. I’d love to hear from you how you have implemented the command design pattern. Any other feedback is always appreciated.
Hello.This post was really fascinating, partjcularly since I was
browsing for thoughts on this subject lasst Tuesday.
Thanks. Appreciate your feedback. I am trying to create videos and blogs on several topics of interest. If you’d like me to cover a specific topic or have a question about a post, please feel free to post a question or comment.
Wow, this post is fastidious, my sister is analyzing these kinds of things,
thus I am going to tell her.
Appreciate your feedback. If you wish to see a post on any other software design pattern, please let me know.