Click here to Skip to main content
15,868,019 members
Articles / Desktop Programming / MFC

Command Pattern at Work in a Database Application

Rate me:
Please Sign up or sign in to vote.
4.70/5 (16 votes)
8 Feb 2011CPOL6 min read 37.9K   457   36   7
A practical example of command pattern applied in a database application
screenshot.JPG

Introduction

In the modern world of programming, the command pattern is one of the most frequently uttered word and has received nearly an oscar for its contribution in implementing undo/redo feature in world class apps. However, a practical example of this design pattern is found once in a blue moon if you get lucky (atleast I wasn't that lucky). There are lots of papers, books or sites that deal with the theoretical aspect of this pattern, but none comes up with a concrete example of it at a practical field. So, this article is a demonstration of my very own implementation of command pattern and that too in a database app. What were you expecting? A drawing app with rectangle/ellipse or bitmap draw and undo the draw and redo again :) ..?

Background

In one of my projects, I had to implement a command processor which acted on the guidelines of command pattern to implement several undo/redo steps for certain operations. Later, in one of my colleague's project (which was related to database), work flow serialization was required where after performing several database operations, it was needed to roll back few steps and start from a previous point. That gave me the idea to apply the command pattern on a database app and see the special effect of the rollback and roll on feature. And no!! my genius code wasn't applied on that real database project since I have done it in a very small scale, and the original app's context was different and the scale was huge. Mind you, the concept can still be applied for huge apps.

The Command Processor Class

To implement the command pattern, first what is required - is a command processor class. This class will be the owner of the commands, and manage the execution and roll back of the commands.

In order to encapsulate a command, I have to come up with a useful abstract class, from where all the application specific commands will be derived from. I named the base class CGCommand class and it looks like this:

C++
// CGCommand class
class CGCommand
{
public:
	CGCommand(){ }
	virtual ~CGCommand() { }

	// Override this to perform a command
	//
	// Returns TRUE if successful, otherwise FALSE
	virtual BOOL Execute() = 0;

	// Override this to undo a command
	//
	// Returns TRUE if successful, otherwise FALSE
	virtual BOOL UnExecute() = 0;

	// Override this to determine whether a command can be undo
	//
	// Returns TRUE if successful, otherwise FALSE
	virtual BOOL CanUndo()
	{
		return TRUE;
	}

	// Override this to determine whether a command can be redo
	//
	// Returns TRUE if successful, otherwise FALSE
	virtual BOOL CanRedo()
	{
		return TRUE;
	}

	// Override this to serialize a command
	// (Not implemented in the command processor level yet)
	//
	// Returns TRUE if successful, otherwise FALSE
	virtual BOOL Load()
	{
		return FALSE;
	}

	// Override this to deserialize a command
	// (not implemented in the command processor level yet)
	//
	// Returns TRUE if successful, otherwise FALSE
	virtual BOOL Save()
	{
		return FALSE;
	}
}; 

I hope the comments are self explanatory and the class is too simple to explain. Now that I have given you a glimpse of what the command class looks like, let's have a look at the grand class The Processor of Commands and its APIs:

C++
// Command Processor is the Owner of Commands,
// therefore it is responsible for clearing the memory allocated for commands
class CGCommandProcessor
{
public:
 
 CGCommandProcessor();
 virtual ~CGCommandProcessor();
 // if bStore is true then the command is executed and also
 // saved in the command chain for undoing purposes
 // if bReleaseIfNotStored is TRUE then the command is deleted from memory 
 // when it is not stored
 //(due to command execution failure or bStore set as FALSE)
 virtual BOOL Submit(CGCommand* pCommand, BOOL bStore = TRUE, 
	BOOL bReleaseIfNotStored = TRUE);
 
 // Store a Command in the command chain without executing it
 virtual BOOL Store(CGCommand* pCommand);
 
 // Retrieve the current command
 //
 //  Returns the current command
 CGCommand* GetCurrentCommand();
 
 // Retrieve the next command
 //
 //  Returns the next command
 CGCommand* GetNextCommand();
 
 // Determine whether a command can be undo
 //
 //  Returns TRUE if successful, otherwise FALSE
 virtual BOOL CanUndo();

 
 // Determine whether a command can be redo
 //
 //  Returns TRUE if successful, otherwise FALSE
 virtual BOOL CanRedo();

 
 // Execute the undo operations
 //
 //  Returns TRUE if successful, otherwise FALSE
 virtual BOOL Undo();
 
 // Execute the redo operations
 //
 //  Returns TRUE if successful, otherwise FALSE
 virtual BOOL Redo();
 
 // Set the size of command array
 //
 // nMaxNoCommands : Specifies the array size
 //
 void SetCommandsArrayMaxSize(UINT_PTR nMaxNoCommands)
 {
  m_nMaxNoCommands = nMaxNoCommands;
 }
 
 // Retrieve the size of command array
 //
 //  Returns the size of command array
 //
 UINT_PTR GetCommandsArrayMaxSize()
 {
  return m_nMaxNoCommands;
 }
 
 // No Bounds Checking for Clear Commands, so use at your own risk
 void ClearCommands();

 // When ClearCommands is invoked, the CurrentCommandIndex position
 //is set to nStartIndex - 1
 void ClearCommands(INT_PTR nStartIndex, INT_PTR nCount = 1); 
....
....
};

So, I guess you are almost clear about what the command processor does. It takes a command through the Submit API and executes and stores (if specified) the command. Then you can invoke the undo and redo APIs which ends up calling the command object's UnExecute and Execute APIs. The current command location in the command chain and its execution and unexecution is maintained by the processor as you'd expect. A practical example now should clarify the usage of the command class and its processor more adequately.

The Database Commands

As I have mentioned before, to encapsulate an application command, you have to implement the abstract class CGCommand and override mainly the Execute and UnExecute APIs. In my database application, there are mainly three major operations: Add, Delete and Edit a row. Although I am working with single row addition, deletion and edition, the concept can easily be extended to account for multiple rows. Following are my 3 classes respectively for Addition, Deleting and Editing rows:

C++
// CGDBAddRowCommand class
class CGDBAddRowCommand : public CGDataBaseCommand
{
public:
 CGDBAddRowCommand(_RecordsetPtr pSet, const CDDXFields& fields, CDataGrid* pGrid); 
 // Perform a command
 //
 //  Returns TRUE if successful, otherwise FALSE
 BOOL Execute();
 // Undo a command
 //
 //  Returns TRUE if successful, otherwise FALSE
 BOOL UnExecute(); 
 CDataGrid* m_pGrid;
};
C++
// CGDBDeleteRowCommand class
// CGDBDeleteRowCommand is nothing but CGDBAddRowCommand with reverse action
class CGDBDeleteRowCommand : public CGDBAddRowCommand
{ 
public:
 CGDBDeleteRowCommand(_RecordsetPtr pSet, const CDDXFields& fields, CDataGrid* pGrid);
 // Perform a command
 //
 //  Returns TRUE if successful, otherwise FALSE
 BOOL Execute();
 // Undo a command
 //
 //  Returns TRUE if successful, otherwise FALSE
 BOOL UnExecute();
protected:
 BOOL m_bFirstTimeExecution;
};
C++
// CGDBEditRowCommand class
class CGDBEditRowCommand : public CGDataBaseCommand
{
public:
 CGDBEditRowCommand(_RecordsetPtr pSet, const CDDXFields& fields, CDataGrid* pGrid); 
 // Perform a command
 //
 //  Returns TRUE if successful, otherwise FALSE
    BOOL Execute();
 // Undo a command
 //
 //  Returns TRUE if successful, otherwise FALSE
    BOOL UnExecute();
 CDDXFields m_undoDDXFields;
 CDataGrid* m_pGrid; 
};

All of the above classes are inherited from CGDataBaseCommand class which is just a derivation from CGCommand class with some extra attributes. Following is that class's declaration:

C++
class CGDataBaseCommand : public CGCommand
{
public: 
 CGDataBaseCommand(_RecordsetPtr pSet, const CDDXFields& fields)
 {
  m_pSet = pSet;  
  m_DDXFields.RemoveAll();
  m_DDXFields.Append(fields);  
  
 } 
 
protected:
 _RecordsetPtr m_pSet;  
 CDDXFields m_DDXFields;  
 
};

If you look at the implementation of all the above mentioned classes, you'd see the CGDBAddRowCommand class in its Execute method uses the record set pointer m_pSet to add a row in a data table. However, it saves the book mark of the newly created row in a conceptual map called m_mapBookMarksVsCommands (which actually is implemented by CArray of MFC). Then, when the UnExecute method is invoked, it retrieves the book mark of the row from that map (where the key for retrieving the bookmark is the command object itself) and deletes the row which corresponds to the bookmark. There is a small tricky part where a function UpdateBookMarks is called upon after we call UnExecute and then Execute to add the recently deleted row again (i.e., redo after undo in short). What the UpdateBookMarks function does is, it finds the previously saved bookmark of the row (when Execute was called initially) and replaces that by the newly created row bookmark (when Execute is called again after an UnExecute). We need to do this, because the initial bookmark would be nothing but a dangling pointer after undoing (UnExecute) happened. Ok, enough with Adding Row. Let's move on to deleting row.

CGDBDeleteRowCommand class is nothing but the reverse version of the CGDBAddRowCommand class and its implementation is nothing more than doing the reverse of the latter which would be evident if you simply browse through the source code for this class.

Lastly, the CGDBEditRowCommand class edits a row with the member recordset pointer in its Execute method and at the same time saves the previous info of the row so that it can undo the edition. All it has to do is edit the row to these saved values when UnExecute is invoked. This class also saves the row book mark inside Execute so that it can come back to the exact row when the undoing is to be executed.

My real intention in this article is to give a feel of how a command can be encapsulated for undo and redo-ing purposes and although the operations that I implemented are pretty trivial, the intelligent reader, when it comes to database apps can use the recordset (or in case of C# and other advanced languages, use something similar but more likable than the recordset) for much advanced purposes.

Using the Code

Here is an example of the declaration and getting a reference of the command processor (a useful function to get the singleton instance of the command processor):

C++
CGCommandProcessor& AppGetCommandProcessor()
{
	static CGCommandProcessor g_commandProcessor;
	return g_commandProcessor;
}		

An example of submitting, executing and storing of a command:

C++
if(bAddData)
{
	AppGetCommandProcessor().Submit( new CGDBAddRowCommand
             (m_pSet, m_DDXFields, m_pGrid) );   
}
else
{
	AppGetCommandProcessor().Submit( new CGDBEditRowCommand
             (m_pSet, m_DDXFields, m_pGrid) );
}

Points of Interest

After deleting the last row, the recordset pointer may point to EOF, so you have to make an extra call to MovePrevious - a record set pointer API to point to a valid row. This application wasn't tested with a fully empty data table, so beware of a possible crash. If such a crash really occurs, I hope somebody would be kind enough to put appropriate checking in the BOOL CGDBAddRowCommand::UnExecute() method and notify me about the appropriate solution. Thanks in advance.

Acknowledgements

My acknowledgements go to Mr. Ariful Huq (Enosis Solutions) for sharing some of his project details with me which gave me the idea to implement the command pattern concept in the database realm.

I heartily thank Kirill Panov (http://www.codeproject.com/script/Membership/View.aspx?mid=30110) for his classy implementation of CDataGrid and the relevant app (http://www.codeproject.com/KB/miscctrl/datagrid.aspx) which made it very easy for me to apply the command pattern concept in a database application.

History

  • Article uploaded: 8th Feb, 2011

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Kotha Technologies
Bangladesh Bangladesh
If you are not in - you are out !
- Chapter 1

Comments and Discussions

 
QuestionNice explanation... Pin
Musthafa (Member 379898)13-Mar-13 22:09
Musthafa (Member 379898)13-Mar-13 22:09 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey26-Feb-12 20:53
professionalManoj Kumar Choubey26-Feb-12 20:53 
GeneralMy vote of 5 Pin
Harrison H8-Feb-11 6:37
Harrison H8-Feb-11 6:37 
GeneralRe: My vote of 5 Pin
Mukit, Ataul8-Feb-11 17:03
Mukit, Ataul8-Feb-11 17:03 
GeneralRe: My vote of 5 Pin
Luc3411-Feb-11 6:20
Luc3411-Feb-11 6:20 
GeneralMy vote of 5 Pin
Humayun Kabir Foysol8-Feb-11 0:27
Humayun Kabir Foysol8-Feb-11 0:27 
GeneralRe: My vote of 5 Pin
Mukit, Ataul8-Feb-11 17:03
Mukit, Ataul8-Feb-11 17:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.