模版方法模式是我们非常常用的几种模式之一。
它定义一个算法中的操作框架,而将一些步骤延迟到子类中。使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。
解决的问题:
提供代码复用性
将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中-
实现了反向控制
通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 & 符合“开闭原则”
下面举出一个例子来说明该模式。
在TCP通讯中,我们通常设计一个通用的数据协议包,这个数据协议包基本上由公用的包头+包体+公用的包尾组成,每一个通讯指令只有包体是不同的,包头和包尾是相同的。而在TCP通讯中,我们需要对这种协议的数据包进行解析。
Java的TCP通讯,我们最常用的是netty框架,下面以netty框架为基础,说说我们如何通过使用模版方法模式来实现对TCP通讯的数据包进行解析。
首先是一个抽象的父类:@Slf4j
public abstract class CashboxBaseEncoder {
public final void encode(CashboxBaseData baseData, ByteBuf out)
{
encodeHeader(baseData, out);
encodeBody(baseData, out);
encodeTail(baseData, out);
}
//共10个字节,其中4个字节属于包头,其余type和时间属于包体
private void encodeHeader(CashboxBaseData baseData, ByteBuf out)
{
log.info("开始写数据包头...");
out.writeByte(baseData.getBeginDLE());
out.writeByte(baseData.getStx());
if (baseData.getType() != ENQ)
{
out.writeShortLE(baseData.getLength());
out.writeByte(baseData.getType().getId());
out.writeByte(baseData.getSendTime().getMonthValue());
out.writeByte(baseData.getSendTime().getDayOfMonth());
out.writeByte(baseData.getSendTime().getHour());
out.writeByte(baseData.getSendTime().getMinute());
out.writeByte(baseData.getSendTime().getSecond());
}
}
//共7个字节
private void encodeTail(CashboxBaseData baseData, ByteBuf out)
{
log.info("开始写数据包尾...");
out.writeMediumLE(baseData.getId());
if (baseData.getType() != ENQ) {
out.writeByte(baseData.getEndDLE());
out.writeByte(baseData.getEtx());
out.writeShortLE(getCheckCode(out, baseData.getLength()));
}
}
protected abstract void encodeBody(CashboxBaseData baseData, ByteBuf out);
}
这就是一个典型的模版方法模式的父类。
我们有一个心跳协议的子类, 大概是这样实现的:
@Slf4j
public class CashboxHeartbeatEncoder extends CashboxBaseEncoder {
@Override
public void encodeBody(CashboxBaseData baseData, ByteBuf out)
{
log.info("进入心跳包写入...");
log.debug("心跳包内容:"+baseData.toString());
out.writeByte(baseData.getSendTime().getMonthValue());
out.writeByte(baseData.getSendTime().getDayOfMonth());
out.writeByte(baseData.getSendTime().getHour());
out.writeByte(baseData.getSendTime().getMinute());
out.writeByte(baseData.getSendTime().getSecond());
}
}
同时,我们有一个版本查询协议的子类,是这样实现的:
@Slf4j
public class CashboxVersionCommandEncoder extends CashboxBaseEncoder {
@Override
public void encodeBody(CashboxBaseData baseData, ByteBuf out)
{
log.info("进入查看版本号命令写入...");
CashboxVersionReadCommandData data = (CashboxVersionReadCommandData)baseData;
out.writeByte(data.getModuleType());
out.writeByte(data.getSubModuleType());
}
}
一个解析心跳的客户端代码:
CashboxBaseEncoder encoder = new CashboxHeartbeatEncoder();
encoder.encode(baseData, out);
上述代码的类图如下所示:
上述的功能,用函数式编程实现,显得更为简单和合理。
首先,实现一个函数式接口:
@FunctionalInterface
public interface BodyEncoder
{
public void encodeBody(CashboxBaseData baseData, ByteBuf out);
}
上面的“CashboxBaseEncoder”将被改造成如下的代码:
@Slf4j
public class CashboxEncoder {
public final void encode(CashboxBaseData baseData, ByteBuf out, BodyEncoder bodyEncoder)
{
encodeHeader(baseData, out);
bodyEncoder.encodeBody(baseData, out);
encodeTail(baseData, out);
}
//共10个字节,其中4个字节属于包头,其余type和时间属于包体
private void encodeHeader(CashboxBaseData baseData, ByteBuf out)
{
log.info("开始写数据包头...");
out.writeByte(baseData.getBeginDLE());
out.writeByte(baseData.getStx());
if (baseData.getType() != ENQ)
{
out.writeShortLE(baseData.getLength());
out.writeByte(baseData.getType().getId());
out.writeByte(baseData.getSendTime().getMonthValue());
out.writeByte(baseData.getSendTime().getDayOfMonth());
out.writeByte(baseData.getSendTime().getHour());
out.writeByte(baseData.getSendTime().getMinute());
out.writeByte(baseData.getSendTime().getSecond());
}
}
//共7个字节
private void encodeTail(CashboxBaseData baseData, ByteBuf out)
{
log.info("开始写数据包尾...");
out.writeMediumLE(baseData.getId());
if (baseData.getType() != ENQ) {
out.writeByte(baseData.getEndDLE());
out.writeByte(baseData.getEtx());
out.writeShortLE(getCheckCode(out, baseData.getLength()));
}
}
}
然后,就可以写客户端代码了。
一个解析心跳的客户端代码如下:
CashboxEncoder encoder = new CashboxEncoder();
encoder.encode(baseData1, out1, (baseData, out) -> {
log.info("进入心跳包写入...");
log.debug("心跳包内容:"+baseData.toString());
out.writeByte(baseData.getSendTime().getMonthValue());
out.writeByte(baseData.getSendTime().getDayOfMonth());
out.writeByte(baseData.getSendTime().getHour());
out.writeByte(baseData.getSendTime().getMinute());
out.writeByte(baseData.getSendTime().getSecond());
});
可以看到,由函数式编程实现的模版方法模式,省了很多子类,节省了大量的代码,同时,也带来了相当大的灵活性。
值得使用函数式编程来改造这样的设计模式!