美文网首页Java8与函数式编程
函数式编程下的模版方法模式

函数式编程下的模版方法模式

作者: 小七赛文 | 来源:发表于2018-03-13 03:55 被阅读0次

模版方法模式是我们非常常用的几种模式之一。
它定义一个算法中的操作框架,而将一些步骤延迟到子类中。使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。
解决的问题:

  1. 提供代码复用性
    将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中

  2. 实现了反向控制
    通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 & 符合“开闭原则”
    下面举出一个例子来说明该模式。
    在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());
});

可以看到,由函数式编程实现的模版方法模式,省了很多子类,节省了大量的代码,同时,也带来了相当大的灵活性。
值得使用函数式编程来改造这样的设计模式!

相关文章

  • 函数式编程下的模版方法模式

    模版方法模式是我们非常常用的几种模式之一。它定义一个算法中的操作框架,而将一些步骤延迟到子类中。使得子类可以不改变...

  • 编程范式 (Programming paradigm)

    编程范式 Programming paradigm 范,模范、典范也。范式即模式、方法。常见的编程范式有:函数式编...

  • 函数式编程

    函数式编程:Functional Programming 1 基本解释: 函数式编程 是一种思维模式,一种编程思想...

  • Lambda函数的演化过程

    lambda函数的演化 本质属于函数式编程,目的是简化编程,减少模版代码 目的 避免匿名内部类定义过多 必要条件 ...

  • freeCodeCamp 旅途8 - 函数式编程

    函数式编程 函数式编程是一种基于函数计算的软件开发方法。像数学一样,函数在编程中通过输入产生输出。函数式编程遵循几...

  • 用 Kotlin 的函数式编程 替代 GOF 设计模式

    用 Kotlin 的函数式编程 替代 GOF 设计模式 函数式编程(FP) 《Kotlin极简教程》正式上架: 点...

  • 函数式编程:柯理化函数和组合函数

    柯理化函数和组合函数都归属于函数式编程,用于解决函数式编程的问题 首先先说明一下函数式编程和面向对象式编程的优缺点...

  • 函数式编程从零到一

    从零到一:很纯的函数式 从干活的角度看待函数式编程和一般的编程方法 函数addOne是一个函数式编程的写法 函数a...

  • 简单的函数式编程Demo

    函数式编程思想:把操作尽可能的写成一系列的函数嵌套或者方法调用; 函数式编程本质:就是往方法中传入block,方法...

  • <<现代C++实战30讲>>打卡学习笔记—提高篇

    说明 提高篇11讲主要学习泛型编程、面向对象编程、元模版编程、函数式编程、并发。每日打卡更新。 打卡Day10:到...

网友评论

    本文标题:函数式编程下的模版方法模式

    本文链接:https://www.haomeiwen.com/subject/gdgnfftx.html