任何的數(shù)據(jù)協(xié)議,只要是稱得上是協(xié)議,就會(huì)有固定的格式。
比如,如下的一個(gè)數(shù)據(jù)協(xié)議,應(yīng)該是一個(gè)相當(dāng)復(fù)雜的數(shù)據(jù)協(xié)議:

這個(gè)協(xié)議,可以用“包頭+包體+包尾”這樣個(gè)格式。其中,包頭和包尾的數(shù)據(jù)長(zhǎng)度是固定的,變化的只是包體長(zhǎng)度。
來解析或者編碼這樣一個(gè)數(shù)據(jù)協(xié)議,我們首先想到的是應(yīng)用模板方法模式,因?yàn)椴还馨w的解析怎么變,包頭和包尾的解析是不變的,因此,我們可以把對(duì)包頭和包尾的解析放到父類里,而把對(duì)包體的解析延遲到子類完成?;谶@樣一個(gè)編程思路,就是模板方法模式的應(yīng)用了。
下面來看它的具體實(shí)現(xiàn):
首先是解析包頭-
@Slf4j
public abstract class CashboxBaseDecoder {
//只給讀寫錢箱參數(shù)命令返回解析使用
@Setter
private CashboxParamHandleType handleType;
@Setter
private CashboxCommandType commandType;
//已經(jīng)獲取了7個(gè)字節(jié)
private CashboxBaseData decodeHeader(ByteBuf buffer) {
log.info("開始解析數(shù)據(jù)包頭...");
logByteBuf(buffer, log);
CashboxBaseData result;
CashboxBaseData baseData = new CashboxBaseData();
baseData.setBeginDLE(buffer.getByte(0));
baseData.setStx(buffer.getByte(1));
baseData.setLength(buffer.getShortLE(2));
baseData.setType(this.commandType);
//解析返回結(jié)果和錯(cuò)誤碼
CashboxBaseResultData cashboxBaseResultData = new CashboxBaseResultData(baseData);
cashboxBaseResultData.setResult(getById(buffer.getByte(4)).get());
cashboxBaseResultData.setErrorCode(buffer.getShortLE(5));
result = cashboxBaseResultData;
return result;
}
值得注意的是,在解析包頭的方法decodeHeader中,除了解析了包頭的4個(gè)字節(jié)以外,還解析了包體的3個(gè)字節(jié),因?yàn)榘w開頭的3個(gè)字節(jié)-結(jié)果和錯(cuò)誤碼,也在每一個(gè)數(shù)據(jù)包中是一樣的,所以,雖然這3個(gè)字節(jié)在包體中,我們?nèi)匀话阉诺搅税^中解析。
接著解析包尾-
private void decodeTail(CashboxBaseData baseData, ByteBuf buffer) throws DataAlteredException{
log.info("開始解析數(shù)據(jù)包尾...");
int beginPort = 4 + baseData.getLength();
baseData.setId(buffer.getMediumLE(beginPort));
baseData.setEndDLE(buffer.getByte(beginPort + 3));
baseData.setEtx(buffer.getByte(beginPort + 4));
baseData.setCrc(buffer.getUnsignedShortLE(beginPort + 5));
System.out.println(getCheckCode(buffer, baseData.getLength()));
if (baseData.getCrc() != getCheckCode(buffer, baseData.getLength())) {
throw new DataAlteredException(ErrorCode.ERROR_PROTO_CHECKSUM, "CRC校驗(yàn)不正確!");
}
}
包尾解析7個(gè)字節(jié),任何數(shù)據(jù)包都一樣。最后,做一下CRC校驗(yàn)。
對(duì)包體的解析在父類里無法實(shí)現(xiàn),抽象到子類中實(shí)現(xiàn)-
protected abstract CashboxBaseData decodeBody(CashboxBaseData baseData, ByteBuf buffer, CashboxParamHandleType handleType);
最后是對(duì)整個(gè)數(shù)據(jù)包的解析,先調(diào)用對(duì)包頭的解析,然后把結(jié)果賦給對(duì)包體解析的方法-
public final CashboxBaseData decode(ByteBuf buffer) throws DataAlteredException{
CashboxBaseData baseData = decodeBody(decodeHeader(buffer), buffer, handleType);
后調(diào)用對(duì)包尾的解析-
decodeTail(baseData, buffer);
log.debug("解析結(jié)果, 公共數(shù)據(jù):"+baseData.toString());
return baseData;
}
}
這就完成了對(duì)父類的編碼。
下面,我們來看看子類的實(shí)現(xiàn)。
第一個(gè)子類是“初始化結(jié)果返回”:
@Slf4j
public class CashboxInitialiseResultDecoder extends CashboxBaseDecoder{
@Override
protected CashboxBaseData decodeBody(CashboxBaseData baseData, ByteBuf buffer, CashboxParamHandleType handleType) {
log.info("進(jìn)入初始化結(jié)果返回?cái)?shù)據(jù)解析...");
return baseData;
}
}
初始化結(jié)果返回的包體沒有任何內(nèi)容。
下面一個(gè)子類是“獲取固件版本結(jié)果返回”:
@Slf4j
public class CashboxInitialiseResultDecoder extends CashboxBaseDecoder{
@Override
protected CashboxBaseData decodeBody(CashboxBaseData baseData, ByteBuf buffer, CashboxParamHandleType handleType) {
log.info("解析固件版本號(hào)結(jié)果...");
CashboxVersionReadResultData data = new CashboxVersionReadResultData((CashboxBaseResultData) baseData);
data.setVersion(buffer.getCharSequence(RESULT_PORT_BEGIN, data.getLength() - SIZE_OF_DATA_4_CODE, Charset.forName("utf-8")).toString());
return data;
}
}
獲取固件版本號(hào)的包體里只有一個(gè)數(shù)據(jù),就是版本號(hào),是字符串形式的。
以上就是整個(gè)數(shù)據(jù)包解析應(yīng)用模板方法模式的解決過程,其類圖如下所示:
