Introduction
This article is all about creating a generic, extensible concept through which a class can respond to commands fired by its clients. The concept revolves around the CommandHandler which handles a Command which has configuration information and works on specific data which is required by the CommandHandlerRoutine which is implemented by the CommandHandler. We also emphasise is on keeping the CommandHandler, Command, and the Data generic, extensible, and flexible. This article is divided into the following sections:
- Motivation
- Where this concept can be used
- Concept explanation
- Major components involved in implementing the concept
- Overall class structure design
- Explanation of classes and their members
- How to create ActualCommandHandler
- How clients can fire the commands to ActualCommandHandler
1. Motivation
In my day-to-day routine development, there was a requirement were I needed something which could respond to commands that are fired from outside, probably from other parts of code or other sub modules. I gave a thought that this something should be a class, because a class can hold necessary data, provide proper encapsulation, and can serve as an abstraction for this requirement. Therefore, I tried to convert a class into a CommandHandler which could efficiently handle/respond to calls/commands of other entities.
2. Where this concept can be used
This concept can be utilized in developing server code, since servers respond to calls/commands of clients. Moreover, the class structure described in this article can find a place in implementing any client-server architecture.
3. Concept explanation
There are two frequently used terms to discuss the concepts mentioned in this article:
- CommandHandler: This will be the class which should follow the specifications discussed in this article in order to be a CommandHandler class.
- Client(s): All the other code which will utilize the CommandHandler or fire commands to the CommandHandler.
A CommandHandler will mostly get utilized in a multithreaded environment wherein all its clients will continuously fire commands. A CommandHandler can respond to commands in two ways:
- SendCommand: In this way, the clients fire the commands to the CommandHandler, and if the CommandHandler is idle, then the commands get responded to/executed quickly. If the CommandHandler is busy processing other commands, then the client needs to wait till the CommandHandler respondsto /executes the command. The client cannot continue its processing or fire another command till the previously sent command is completely executed in the CommandHandler.
- PostCommand: In the case of PostCommand, the Client does not need to wait for the completion of the execution of a command. It can continue its processing or fire another command while the previously posted command is getting executed in the CommandHandler.
This way, the command fired by clients are first stored in a queue which is present in the CommandHandler. The CommandHandler fetches the commands from the queue and processes them one by one in a separate thread. Depending on their priority, the commands are temporarily stored in one of the high, normal, or low priority command queues. Commands are executed according to their priority. Therefore, all the high priority commands will get executed before the normal priority commands, which will be executed before the low priority commands. By the way, commands are inserted in the queue from the back end, and fetched or removed for processing from the front end. This is as per the standard queue concept.
In the case of SendCommand and PostCommand, the main procedure/method inside of CommandHandler which:
- fetches and removes commands from the queue,
- processes the fetched commands,
- notifies the client regarding the completion of execution of a command.
runs in a separate thread. This main method is CXCommandHandler::CommandProcessor
. Therefore, after converting a class into a CommandHandler, this converted class acts as a worker thread which can continuously execute the commands.
4. Major components involved in implementing the concept
- Template
<typename ACTUALCOMMANDHANDLER>
class CXCommandHandler
- Class
CXCommand
- Class
CXData
- Class
CActualCommandHandler
- this can be any class which needs to get converted to a CommandHandler
- class
COMMAND
enum COMMANDLIST
struct DATA_COMMANDHANDLERROUTINE1
struct DATA_COMMANDHANDLERROUTINE2
struct DATA_COMMAND..
5. Overall class structure design
This class structure is implemented in the "CommandHandler.h" and "CommandHandler.cpp" files. To implement this concept, the Command Design Pattern is used. Clients create the command (instance of class CXCommand
), populate it with the necessary data (instance of the class derived from CXData
), and then fire it to the CommandHandler (instance of class derived from class CXCommandHandler
) which executes those commands.
- Template
<typename ACTUALCOMMANDHANDLER>
class CXCommandHandler
: This is the main component of the overall concept. This is a template class which will serve as the base class for the class which you want to convert to CommandHandler. This class provides and implements all the necessary things required to implement the CommandHandler. - Class
CXCommand
: This is the component which is the medium of communication between the client and the CommandHandler. This class provides the necessary members through which the client can set information such as Command Priority, Command ID, etc., as well as the data that will be required to execute the command. - Class
CXData
: This class serves as the base class for the data which is used in executing individual commands. - Class
CActualCommandHandler
: This is the class which you want to convert to a CommandHandler. This class should derive from the class CXCommandHandler
. The main responsibility of this class is to implement the CommandHandlerRoutines for the commands that will be fired by the clients.
- Class
COMMAND
: This class mainly serves as a namespace to keep all the things related to commands which will be handled by CActualCommandHandler
. This class is declared inside CActualCommandHandler
. This class defines two important things through which the client can populate the commands.
enum COMMANDLIST
: Every CommandHandlerRoutine implemented by CActualCommandHandler
is given a unique ID. COMMANDLIST
holds these CommandIDs.struct DATA_COMMANDHANDLERROUTINE1
: This struct is derived from the class CXData
. This structure specifies the data which will be used by the related CommandHandlerRoutine1
which is implemented by CActualCommandHandler
.struct DATA_COMMANDHANDLERROUTINE2
: This struct is derived from the class CXData
. This structure specifies the data which will be used by the related CommandHandlerRoutine2
which is implemented by CActualCommandHandler
.struct DATA_COMMAND..
Note: CommandHandlerRoutines should strictly have the following signature:
int CommandHandlerRoutineName(CXData *pdata);
6. Explanation of classes and their members
This can be directly found in the source files.
7. How to create ActualCommandHandler
Consider a class CAccountService
. We will convert this class into an ActualCommandHandler which will then be able to respond to the various commands fired by clients (other code). The client code can fire commands to:
- Register/unregister human users for the services provided by
CAccountService
. - Email users their account information.
- Print account information of users.
In general, CAccountService
should be able to handle all the existing commands as well be open to get extended to handle other types of commands which might come up in future.
Now, do the following step by step:
- Create two files AccountService.h and AccountService.cpp.
- Include the existing class structure:
#include "CommandHandler.h"
- Create a class
CAccountService
and derive it from CXCommandHandler
:
#include "CommandHandler.h"
class CAccountService : public CXCommandHandler <CAccountService>
{
};
- Place the existing macro
IMPLEMENT_GETTHISMEMBERFUNCTION
in the public section of the class CAccountService
. This macro can be found in the CommandHandler.h file.
#include "CommandHandler.h"
class CAccountService : public CXCommandHandler <CAccountService>
{
public:
IMPLEMENT_GETTHISMEMBERFUNCTION
};
- Now create a class
COMMAND
inside the public section of CAccountService
. COMMAND
will act as a namespace, and will keep all the details related to commands which will be handled by CAccountService
. Please refer to 5. Overall class structure design for an explanation of the COMMAND
class.
#include "CommandHandler.h"
class CAccountService : public CXCommandHandler <CAccountService>
{
public:
IMPLEMENT_GETTHISMEMBERFUNCTION
public:
class COMMAND
{
};
};
- Now, let's say this class
CAccountService
provides some services which clients can access by firing the respective commands. We need to implement these services, providing routines which are called CommandHandlerRoutines. Therefore, the next step is to implement CommandHandlerRoutines.
Implementing CommandHandlerRoutines requires the following steps:
- Declaring the CommandHandlerRoutine:
#include "CommandHandler.h"
class CAccountService : public CXCommandHandler <CAccountService>
{
public:
IMPLEMENT_GETTHISMEMBERFUNCTION
public:
class COMMAND
{
};
protected:
int Register(CXData *pData = NULL );
};
As you can see, the CommandHandlerRoutine Register
takes a parameter in a very generic form. This parameter is a pointer to the CXData
class, which will actually point to specific data on which the Register
CommandHandlerRoutine will operate.
For example, the Register
CommandHandlerRoutine needs an instance of struct CAccountService::COMMAND::DATA_USERINFO
. Please refer to 5. Overall class structure design for an explanation related to the data requirement of a CommandHandlerRoutine.
- Declaring the data required by a CommandHandlerRoutine.
The Register
CommandHandlerRoutine declared in the above step requires an instance of CAccountService::COMMAND::DATA_USERINFO
. Therefore, create a struct DATA_USERINFO
in the public section of the CAccountService::COMMAND
class which is acting as a namespace.
#include "CommandHandler.h"
class CAccountService : public CXCommandHandler <CAccountService>
{
public:
IMPLEMENT_GETTHISMEMBERFUNCTION
public:
class COMMAND
{
public:
struct DATA_USERINFO : public CXData
{
TCHAR tcszUserName[260];
int iUserID;
};
};
protected:
int Register(CXData *pData = NULL );
};
This struct contains data which is required by the Register
CommandHandlerRoutine to register a human user.
- Declaring the CommandID which will represent the CommandHandlerRoutine.
The ActualCommandHandler, i.e., CAccountService
will expose various CommandHandlerRoutines, each of which should have a unique ID called CommandID. To handle the CommandID management, create an enumeration COMMANDLIST
in the public section of CAccountService::COMMAND
which is acting as a namespace. This enumeration will hold the CommandIDs for the CommandHandlerRoutines implemented by CAccountService
.
#include "CommandHandler.h"
class CAccountService : public CXCommandHandler <CAccountService>
{
public:
IMPLEMENT_GETTHISMEMBERFUNCTION
public:
class COMMAND
{
public:
enum COMMANDLIST
{
Register,
TOTALCOMMANDS };
struct DATA_USERINFO : public CXData
{
TCHAR tcszUserName[260];
int iUserID;
};
};
protected:
int Register(CXData *pData = NULL );
};
Now, clients can access the Register
CommandHandlerRoutine by specifying the CommandID CAccountService::COMMAND::COMMANDLIST::Register
in the command which they will fire to CAccountService
.
- Defining the CommandHandlerRoutine.
Now define the CommandHandlerRoutine Register
which is declared in CAccountService
.
#include "AccountService.h"
int CAccountService::Register(CXData *pData )
{
CAccountService::COMMAND::DATA_USERINFO *pUserInfo =
(CAccountService::COMMAND::DATA_USERINFO *) pData;
if(m_UserDBase.find(pUserInfo->iUserID) != m_UserDBase.end())
return 2;
m_UserDBase[pUserInfo->iUserID] = pUserInfo->tcszUserName;
return 1; }
Since the Register
CommandHandlerRoutine works on CAccountService::COMMAND::DATA_USERINFO
which it will receive as a parameter, we can safely do the typecasting. The existing class structure of CXCommandHandler
makes sure that proper data is passed to the CommandHandlerRoutine. After type casting, user information can be accessed through pUserInfo
.
- Declaring the native data which is required by ActualCommandHandler.
In the definition of the Register
CommandHandlerRoutine, a std::map m_UserDBase
is used by CAccoountService
to store the user information. Such native data or the data required by the ActualCommandHandler can also be kept in the ActualCommandHandler.
#include "CommandHandler.h"
#include <map>
#include <string>
using namespace std;
class CAccountService : public CXCommandHandler <CAccountService>
{
public:
IMPLEMENT_GETTHISMEMBERFUNCTION
public:
class COMMAND
{
public:
enum COMMANDLIST
{
Register,
TOTALCOMMANDS };
struct DATA_USERINFO : public CXData
{
TCHAR tcszUserName[260];
int iUserID;
};
};
private :
map <int, string> m_UserDBase;
protected:
int Register(CXData *pData = NULL );
};
Since the Register
CommandHandlerRoutine works on CAccountService::COMMAND::DATA_USERINFO
which it will receive as a parameter, we can safely do the typecasting. After type casting, user information can be accessed through pUserInfo
.
- Registering the CommandHandlerRoutine in the CommandHandler.
Till now, ActualCommandHandler, i.e., CAccountService
provided the facility of the Register
service or CommandHandlerRoutine by declaring the necessary things like CommandID and the data on which it will operate. Now, this CommandHandlerRoutine needs to be registered in the ActualCommandHandler's database which stores the information of all commands the ActualCommandHandler can respond to. For registering the CommandHandlerRoutine which is implemented by the ActualCommandHandler, call the RegisterCommand
member function which is already implemented by the base class CXCommandHandler
. The RegisterCommand
member function can be called from the constructor of ActualCommandHandler.
#include "CommandHandler.h"
class CAccountService : public CXCommandHandler <CAccountService>
{
public:
IMPLEMENT_GETTHISMEMBERFUNCTION
public:
class COMMAND
{
public:
enum COMMANDLIST
{
Register,
TOTALCOMMANDS };
struct DATA_USERINFO : public CXData
{
TCHAR tcszUserName[260];
int iUserID;
};
};
private :
map <int, string> m_UserDBase;
public:
CAccountService()
{
RegisterCommand(CAccountService::COMMAND::COMMANDLIST::Register,
&CAccountService::Register);
}
protected:
int Register(CXData *pData = NULL );
};
Note: The CommandHandlerRoutine handling/implementing a command should be registered before the clients can fire that particular command.
In this way, we can create other CommandHandlerRoutines like:
Please refer to the files AccountService.h and AccountService.cpp for the implementation of these CommandHandlerRoutines.
8. How clients can fire the commands to the ActualCommandHandler
Command is nothing but an instance of the CXCommand
class. This instance consists of two parts:
- CommandInfo: For an explanation of this, please refer to
struct CXCommand::COMMANDINFO
in the CommandHandler.h file. - CommandData: This is the data which is declared in the
CActualCommandHandler::COMMAND
class. Please refer to 5. Overall class structure design and 6.6.ii Declaring the data required by CommandHandlerRoutine.
Clients create, populate, and fire the commands. Please refer to 3. Concept explanation. For example, lets fire a command to ActualCommandHandler (i.e., CAccountService
) in order to 'Register' a user. For this, create an object of ActualCommandHandler.
CAccountService oService;
Clients can fire commands in one of two ways:
- SendCommand: While using SendCommand, the client waits for the command to be executed. So most of the time, things can be allocated on the stack itself.
- Create the command object:
CXCommand cmd;
- Create the command info object which will be set in the command object. Also, initialize it by using the
CXCommand::GetCommandInfo
member function.
CXCommand cmd;
CXCommand::COMMANDINFO cmdinfo;
cmd.GetCommandInfo(cmdinfo);
- Set the members of the
cmdinfo
object:
CXCommand cmd;
CXCommand::COMMANDINFO cmdinfo;
cmd.GetCommandInfo(cmdinfo);
cmdinfo.enumCommandPriority = CXCommand::PRIORITY::COMMANDPRIORITY_NORMAL;
cmdinfo.enumCommandValidity = CXCommand::VALIDITY::COMMANDVALIDITY_VALID;
cmdinfo.enumExtraInfo =
CXCommand::EXTRA::COMMANDEXTRA_DONOTDELETEONCOMPLETIONOFEXECUTION;
cmdinfo.iCommandID = CAccountService::COMMAND::COMMANDLIST::Register;
_tcscpy(cmdinfo.tcszCommandName, _T("Register"));
- Set the
cmdinfo
object in the command object cmd
.
CXCommand cmd;
CXCommand::COMMANDINFO cmdinfo;
cmd.GetCommandInfo(cmdinfo);
cmdinfo.iCommandID = CAccountService::COMMAND::COMMANDLIST::Register;
cmdinfo.enumCommandPriority = CXCommand::PRIORITY::COMMANDPRIORITY_NORMAL;
cmdinfo.enumCommandValidity = CXCommand::VALIDITY::COMMANDVALIDITY_VALID;
cmdinfo.enumExtraInfo =
CXCommand::EXTRA::COMMANDEXTRA_DONOTDELETEONCOMPLETIONOFEXECUTION;
_tcscpy(cmdinfo.tcszCommandName, _T("Register"));
cmd.SetCommandInfo(cmdinfo);
- Create the related command data on which the CommandHandlerRoutine will operate. Also, assign values to this data.
int Register(CXData *pData = NULL );
It can be observed that the Register
CommandHandlerRoutine expects an instance of struct CAccountService::COMMAND::DATA_USERINFO
.
CXCommand cmd;
CXCommand::COMMANDINFO cmdinfo;
cmd.GetCommandInfo(cmdinfo);
cmdinfo.iCommandID = CAccountService::COMMAND::COMMANDLIST::Register;
cmdinfo.enumCommandPriority = CXCommand::PRIORITY::COMMANDPRIORITY_NORMAL;
cmdinfo.enumCommandValidity = CXCommand::VALIDITY::COMMANDVALIDITY_VALID;
cmdinfo.enumExtraInfo =
CXCommand::EXTRA::COMMANDEXTRA_DONOTDELETEONCOMPLETIONOFEXECUTION;
_tcscpy(cmdinfo.tcszCommandName, _T("Register"));
cmd.SetCommandInfo(cmdinfo);
CAccountService::COMMAND::DATA_USERINFO cmddata;
_tcscpy(cmdddata.tcszUserName, _T("schneider"));
cmddata.iUserID = 10;
- Set the command data in the Command object.
CXCommand cmd;
CXCommand::COMMANDINFO cmdinfo;
cmd.GetCommandInfo(cmdinfo);
cmdinfo.iCommandID = CAccountService::COMMAND::COMMANDLIST::Register;
cmdinfo.enumCommandPriority = CXCommand::PRIORITY::COMMANDPRIORITY_NORMAL;
cmdinfo.enumCommandValidity = CXCommand::VALIDITY::COMMANDVALIDITY_VALID;
cmdinfo.enumExtraInfo =
CXCommand::EXTRA::COMMANDEXTRA_DONOTDELETEONCOMPLETIONOFEXECUTION;
_tcscpy(cmdinfo.tcszCommandName, _T("Register"));
cmd.SetCommandInfo(cmdinfo);
CAccountService::COMMAND::DATA_USERINFO cmddata;
_tcscpy(cmdddata.tcszUserName, _T("schneider"));
cmddata.iUserID = 10;
cmd.SetCommandData(&cmddata);
- Fire the command to the ActualCommandHandler:
CXCommand cmd;
CXCommand::COMMANDINFO cmdinfo;
cmd.GetCommandInfo(cmdinfo);
cmdinfo.iCommandID = CAccountService::COMMAND::COMMANDLIST::Register;
cmdinfo.enumCommandPriority = CXCommand::PRIORITY::COMMANDPRIORITY_NORMAL;
cmdinfo.enumCommandValidity = CXCommand::VALIDITY::COMMANDVALIDITY_VALID;
cmdinfo.enumExtraInfo =
CXCommand::EXTRA::COMMANDEXTRA_DONOTDELETEONCOMPLETIONOFEXECUTION;
_tcscpy(cmdinfo.tcszCommandName, _T("Register"));
cmd.SetCommandInfo(cmdinfo);
CAccountService::COMMAND::DATA_USERINFO cmddata;
_tcscpy(cmdddata.tcszUserName, _T("schneider"));
cmddata.iUserID = 10;
cmd.SetCommandData(&cmddata);
oService.SendCommand(&cmd);
- PostCommand: This method is similar to SendCommand, with the only difference that the client does not wait for the completion of execution of commands which it has fired. In PostCommand, commands are first stored in the appropriate command priority queue, and then a command is fetched from the queue and executed. Meanwhile, the client code is also executing in parallel. Therefore, if a thought is given here then, after posting the command to the ActualCommandHandler, the command and its related data should remain valid, i.e., the memory at which the command and its data are stored should remain valid/accessible. Therefore, while using PostCommand, the Client should allocate the Command object and all its related data on the heap so that they remain valid till the command is executed. But wait. In PostCommand, if the client does not wait for the completion of execution of commands then, who is going to deallocate the memory which is allocated for the Command object and its related data? In this case, the client can give the responsibility of deallocating the memory to the ActualCommandHandler.
CXCommand *pcmd = new CXCommand;
CXCommand::COMMANDINFO cmdinfo;
pcmd->GetCommandInfo(cmdinfo);
cmdinfo.iCommandID = CAccountService::COMMAND::COMMANDLIST::Register;
cmdinfo.enumCommandPriority = CXCommand::PRIORITY::COMMANDPRIORITY_NORMAL;
cmdinfo.enumCommandValidity = CXCommand::VALIDITY::COMMANDVALIDITY_VALID;
cmdinfo.enumExtraInfo = CXCommand::EXTRA::COMMANDEXTRA_DELETEONCOMPLETIONOFEXECUTION;
_tcscpy(cmdinfo.tcszCommandName, _T("Register"));
pcmd->SetCommandInfo(cmdinfo);
CAccountService::COMMAND::DATA_USERINFO *pcmddata =
new CAccountService::COMMAND::DATA_USERINFO;
CXData::DATAINFO stDataInfo;
pcmddata->GetDataInfo(stDataInfo);
stDataInfo.enumOperationOnData = CXData::OPERATIONONDATA::OPERATION_DELETEAFTERUSE;
pcmddata->SetDataInfo(stDataInfo);
_tcscpy(pcmdddata->tcszUserName, _T("schneider"));
pcmddata->iUserID = 10;
pcmd->SetCommandData(pcmddata);
oService.PostCommand(pcmd);
Note
- Once the value
CXCommand::EXTRA::COMMANDEXTRA_DELETEONCOMPLETIONOFEXECUTION
is specified for CXCommand::COMMANDINFO::enumExtraInfo
through the CXCommand::SetCommandInfo
member function, then the Command object should not be used after firing the command either through SendCommand or PostCommand. Once the value CXData::OPERATIONONDATA::OPERATION_DELETEAFTERUSE
is specified for CXData::DATAINFO::enumOperationOnData
through the CXData::SetDataInfo
member function, then the command data should not be used after firing the command either through SendCommand or PostCommand. - On exiting. i.e., when the ActualCommandHandler is about to terminate, all the commands present (if any) in the different priority queues are first executed and then the ActualCommandHandler exits.
History
- Version 1.0:
- Initial version of article.