Decorator Pattern for Protocol-based Data Januari 16, 2008
Posted by kunilkuda in java.add a comment
I will have SCJA (Sun-Certificated Java Associate) certification soon, so I learn again about Java, UML, OODA analysist and design pattern. I found out this old design pattern that suits our protocol-based development (both for gateway and MCU side).
Our current protocol is something like this :
struct WP_Protocol {
uint8_t appId,
uint16_t originAddr,
uint16_t destAddr,
uint32_t timestamp,
uint16_t sensor1,
uint16_t sensor2,
uint16_t sensor3
}
In our previous code, we pack the data inside single function :
void pack(destAddress,sensor1Data, sensor2Data, sensor3Data) {
struct WP_Protocol packedData;
packedData.appId = AGROMONITORING_APP_ID;
packedData.originAddr = getLocalAddr();
packedData.destAddr = destAddress;
packedData.timestamp = time();
packedData.sensor1 = sensor1Data;
packedData.sensor2 = sensor2Data;
packedData.sensor3 = sensor3Data;
// Send to radio
…
}
It’s good and works well. But suppose we want to change the protocol, we will have to modify the structure and the pack(), and test it again (for big-endian / little-endian sequence, etc). So, the pack() will not be re-usable, because each protocol will have their own pack().
I found better idea. Rather than messing around with pack(), with this method (called decorator design pattern), we can have re-usable codes, multiple-protocols, and minimum testing.
Here’s how :
1) Define the layers
struct sensors {
uint16_t sensor1,
uint16_t sensor2,
uint16_t sensor3
}
struct timestampLayer {
uint32_t timestamp,
void * data
}
struct appIDLayer {
uint8_t appId,
void * data
}
struct addressesLayer {
uint16_t originAddr,
uint16_t destAddr,
void * data
}
2) Define the methods to handle the layers
struct sensor packSensor(sensor1Data, sensor2Data, sensor3Data)
{
struct sensors retVal;
retVal.sensor1 = sensor1Data;
retVal.sensor2 = sensor2Data;
retVal.sensor3 = sensor3Data;
return retVal;
}
struct timestampLayer packTimeStamp(void * data)
{
struct timestampLayer retVal;
retVal.timestamp = time();
retVal.data = data;
return retVal;
}
struct appIDLayer packAppId(void * data)
{
struct appIDLayer retVal;
retVal.appId = AGROMONITORING_APP_ID;
retVal.data = data;
return retVal;
}
struct addressesLayer packAddresses(uint16_t destAddr, void * data)
{
struct addressesLayer retVal;
retVal.originAddr = getLocalId();
retVal.destAddr = destAddr;
retVal.data = data;
return retVal;
}
3) To create old structure, just chain the whole functions :
sendToRadio(
packAppId(
packAddresses(0,
packTimeStamp(
packSensor(sensor1Data, sensor2Data, sensor3Data)))));
To create new protocol, where address layer come first before appId (for example) :
sendToRadio(
packAddresses(0,
packAppId(
packTimeStamp(
packSensor(sensor1Data, sensor2Data, sensor3Data)))));
To create new protocol, where new layer added between timestamp and sensors (for example) :
sendToRadio(
packAddresses(0,
packAppId(
packTimeStamp(
newLayer(
packSensor(sensor1Data, sensor2Data, sensor3Data))))));
Anything that you want to change, just change the chain of calling. Because every layer’s function has been tested before, changing sequence won’t need new testing. The only thing that we need to test is new layer that will be inserted.
In summary, decorator pattern will save testing time, faster to deploy, and minimalize bug.