Design Patterns – Using Command Pattern and Dependency Injection Together

cdesign-patternsobject-oriented-design

I have many projects that have essentially the same high level requirement: test all hardware on a device. Each device can have a different communication protocol, requires different test equipment to exercise the device, etc. So, at a high level a test is essentially the following:

  1. obtain handles to test equipment
  2. apply stimulus to device
  3. measure/detect/record something measurable
  4. remove stimulus
  5. store results
  6. handle errors

I thus created an interface class ( CTestEquipment ) as every piece of test equipment can be a concrete class with a unique implementation. The device under test can be considered a piece of test equipment as well. I'm using Dependency Injection for the driver of the test equipment. This is to allow for easy changes from one driver to another and to keep the driver and the equipment somewhat decoupled. For instance, a power supply might use a GPIB driver, a VISA driver, or raw serial commands. Each project can choose which is applicable and change one line ( the driver instantiation ).

Next, I created classes to implement the Command pattern for the test equipment/device. So, CTestEquipment inherits from CCmdReceiver. A concrete command class calls the execute method which via the receiver calls the test equipment's tx/rx methods. Those methods call the underlying driver.

Questions/Problems:
How do I extract the received data from the command? Right now execute has no arguments and returns nothing. A concrete command might only transmit, only receive, or do both so the execute interface needs to either return a value even when there's no received data or some other way I need to be able to access it. CSetSerialNumber has no received data. CGetMode does.

Since the command is calling tx/rx which ultimately calls the driver's tx/rx how do I package the data to those calls since each driver could take something different? I've contemplated a CPacket interface and various concrete implementations but then how do I make sure passing a concrete class that doesn't match the driver is caught?

//--------------------------------------------------------------------------------------
// CCommand
//      Abstract interface that defines the execute method
//--------------------------------------------------------------------------------------
class CCommand
{
protected:
CCmdReceiver *      _rcvr_p;            // receiver interface

// Default Constructor
CCommand()
{}

public:
// Constructor
CCommand
    (
    CCmdReceiver *  r               // receiver interface
    ) :
   _rcvr_p      ( r )
{}

// Destructor
virtual ~CCommand()
{}

// Method to Execute the Command
virtual void execute() = 0;
};

//--------------------------------------------------------------------------------------
// CCmdReceiver
//      Abstract interface that defines the command action
//--------------------------------------------------------------------------------------
class CCmdReceiver
{
public:
virtual void flush() = 0;

virtual void rx( std::vector< unsigned char > & v ) = 0;

//virtual void tx( std::string const & s ) = 0;
virtual void tx( CPacket * p ) = 0;
};

/*------------------------------------------------------------------------------
Test equipment driver abstract base class
------------------------------------------------------------------------------*/
class CDriver
{
public:
/*--------------------------------------------------------------------------
Constructor
--------------------------------------------------------------------------*/
CDriver() :
    _is_open            ( false )
{}

/*--------------------------------------------------------------------------
Destructor
--------------------------------------------------------------------------*/
virtual ~CDriver()
{}

//protected:
/*--------------------------------------------------------------------------
Close the Driver
    Must be implemented in a derived class
--------------------------------------------------------------------------*/
virtual void close() = 0;

/*--------------------------------------------------------------------------
Get the Received Data
--------------------------------------------------------------------------*/
template< typename tVal >
tVal getRxData();

/*--------------------------------------------------------------------------
Determine if the Driver is Open
--------------------------------------------------------------------------*/
virtual bool isOpen()
{
return( _is_open );
}

/*--------------------------------------------------------------------------
Open the Driver
    Must be implemented in a derived class
--------------------------------------------------------------------------*/
virtual void open() = 0;

/*--------------------------------------------------------------------------
Receive Data
--------------------------------------------------------------------------*/
virtual void rx()
{}

/*--------------------------------------------------------------------------
Set Open/Closed Flag State
--------------------------------------------------------------------------*/
inline void setOpenState
    (
    bool const & open = true
    )
{
_is_open = open;
}

/*--------------------------------------------------------------------------
Transmit Data
--------------------------------------------------------------------------*/
virtual void tx
    (
    std::string const & cmd         // command to transmit
    )
{}

private:
bool    _is_open;           // flag to indicate if protocol is open
};

//--------------------------------------------------------------------------------------
// Abstract Test Equipment Class
//      Utilizes constructor DI for device driver
//      Utilizes RAII for open/close
//--------------------------------------------------------------------------------------
class CTestEquipment : public CCmdReceiver
{
public:
/// Constructor
CTestEquipment
    (
    boost::shared_ptr< CDriver > const &
                drvr        // device driver
    ) :
    _drvr           ( drvr ),
    _init_cnt           ( 0 )
{
_drvr->open();
incInitCnt();
}

/// Destructor
virtual ~CTestEquipment()
{
if( isInitialized() )
     {
     _drvr->close();
     }
}

/// Function to Access the Equipment's Driver
inline boost::shared_ptr< CDriver > const & accessDriver() const
{
return( _drvr );
}   // accessDriver()

/// Decrement the Initialization Count
inline void decInitCnt()
{
--_init_cnt;
}   // decInitCnt()

/// Get the Initialization Count
inline int getInitCnt() const
{
return( _init_cnt );
}   // getInitCnt()

/// Increment the Initialization Count
inline void incInitCnt()
{
++_init_cnt;
}   // incInitCnt()

/// Check if Initialization has Occurred
inline bool isInitialized() const
{
return( _init_cnt >= 1 );
}   // isInitialized()

/// Determine if the Driver is Open
inline bool isOpen() const
{
return( _drvr->isOpen() );
}   // isOpen() 

 protected:
/// Reset the Equipment
virtual void reset()
{
//_drvr->reset();
}

/// Function to Receive Data from the Equipment
inline void rx()
{
_drvr->rx();
}

/// Function to Set the Device to a Default State
virtual void setToDflts()
{}

/// Function to Transmit a Command to the Equipment
inline void tx( std::string const & cmd )
{
_drvr->tx( cmd );
}

/// Function to Transmit a Command, Receive the Response, and Return the Parsed    Value
template< typename tVal >
tVal txRx
    (
    std::string const & cmd         // command to transmit
        )
{
_drvr->tx( cmd );
_drvr->rx();

return( _drvr->getRxData< tVal >() );
}

protected:
boost::shared_ptr< CDriver > const &
        _drvr;              // device driver
int     _init_cnt;          // initialization count
};

class CGetMode : public CCommand
{
public:
CGetMode
    (
    CCmdReceiver *  rcvr            // command receiver
    );

void execute()
    {
    // G: what type is cmd?
    // G: what type is buffer?
    // G: how do we access buffer?
    _rcvr_p->flush();
    _rcvr_p->tx( cmd );
    _rcvr_p->rx( buffer );
    }
 };

//--------------------------------------------------------------------------------------
// CSetSerialNumber
//     Extends command interface. Implements the execute method and invokes it on a receiver.
//--------------------------------------------------------------------------------------
class CSetSerialNumber : public CCommand
{
public:
// Constructor
CSetSerialNumber
    (
    CCmdReceiver *  rcvr                // command receiver
    );

void execute()
    {
    // G: what type is cmd?
    _rcvr_p->tx( cmd );
    }
};

Best Answer

You need a data transfer protocol. It can be as simple as a string with comma-separated values, or as complex as some XML backed up by a schema. Whatever you decide, the receiver will have to be smart enough to decode it.

As an example, one of your commands could retrieve a comma-separated header:

TIME, ALTITUDE, TEMPERATURE, AIRSPEED, WHATEVER

This tells you not only the name of each signal, but how many signals there are. You can use this information to set up your decoder on the receiver side.

You could also use something like Protocol Buffers.