设计模式--模板方法模式

模板方法模式

定义

定义一个操做中的算法的框架,而将一些步骤延迟到子类中。使得子类能够不改变一个算法的结构便可重定义该算法的某些特定步骤。算法

通用类图

模板方法通用类图

意图

定义一个操做中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类能够不改变一个算法的结构便可重定义该算法的某些特定步骤。安全

优势

  1. 封装不变部分,扩展可变部分。
  2. 提取公共代码,便于维护。
  3. 行为由父类控制,子类实现。

缺点

按照咱们的设计习惯,抽象类负责声明最抽象、最通常的事物属性和方法,实现类完成具体的事物属性和方法。可是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目中,会带来代码阅读的难度,并且也会让新手产生不适感。并发

应用场景

  1. 多个子类有公有的方法,而且逻辑基本相同时。
  2. 重要复杂的算法,能够把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
  3. 重构时,模板方法模式是一个常常使用的模式,把相同的代码 抽取到父类中。

扩展

钩子函数,经过钩子函数可让子类决定父类的行为。框架

说明

模板方法其实主要是定义一个通用的算法框架(这里的算法说得是为解决事情而须要执行的一系列步骤),将通用的抽象的部分定义在父类中,变化的具体的部分只是在父类中声明抽象的方法,由子类去实现具体的内容。ide

话很少说上代码,这里只为了演示,并发线程安全的问题暂不作考虑。函数

在由事件驱动的系统中,确定会用到消息队列,这里模拟一个利用消息队列处理平时系统产生的各类消息(用户注册,下订单,上传产品,扣减库存等)的场景。this

假设有一个全部消息的基本的父类线程

class BaseMsg {
    Date date;

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

消息A设计

class MsgA extends BaseMsg {

    String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

消息B日志

class MsgB extends BaseMsg{

    String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

用 ConcurrentLinkedQueue 模拟消息队列,现实中可能用到的是 ActiveMQ, RabbitMQ 等

public class CacheBuffer {

    public static final Queue CACHE_BUFFER = new ConcurrentLinkedQueue();

}

下面就是咱们的模版类了

public abstract class AbstractMsgHandler<E extends BaseMsg> {

    private boolean isDo = true;

    protected void put(E e) {
        CACHE_BUFFER.offer(e);
    }

    protected void process() {
        E e = (E) CACHE_BUFFER.poll();

        if (checkValid(e)) {

            setProcessTime(e);

            if (isDo()) {
                doAnotherThing();
            }

            doLogic(e);
        }
    }

    private boolean checkValid(E e) {
        System.out.println("检查字段是否为空,对象是否合法等等");
        return true;
    }

    private void setProcessTime(E e) {
        System.out.println("设置处理时间等于当前时间,记录到日志");
    }

    private void doAnotherThing() {
        System.out.println("作其余的特别的事情");
    }

    protected abstract void doLogic(E e);

    protected boolean isDo () {
        return isDo;
    }

    public void setDo(boolean aDo) {
        isDo = aDo;
    }
}

在模版类中添加了一些通用的方法

put 方法的是添加消息到队列
process 方法的是从消息队列中取出消息,而且检查了有效性(checkValid),设置了处理时间(setProcessTime),还有其余事情(doAnotherThing),接下来是处理业务逻辑(doLogic)

接下来是 消息A的处理类

public class ConcreteMsgAHandler extends AbstractMsgHandler<MsgA> {

    @Override
    protected void doLogic(MsgA msgA) {
        System.out.println("MsgA 处理业务逻辑");
    }

}

消息B的处理类

public class ConcreteMsgBHandler extends AbstractMsgHandler<MsgB> {

    @Override
    protected void doLogic(MsgB msgB) {
        System.out.println("MsgB 处理业务逻辑");
    }
}

各自实现了 doLogic 方法,实现本身的业务逻辑。

场景类

public static void main(String[] args) {
    MsgA msgA = new MsgA();
    msgA.setContent("a");

    MsgB msgB = new MsgB();
    msgB.setContent("b");

    ConcreteMsgAHandler msgAHandler = new ConcreteMsgAHandler();
    ConcreteMsgBHandler msgBHandler = new ConcreteMsgBHandler();
    
    msgAHandler.put(msgA);
    msgBHandler.put(msgB);
    
    msgAHandler.process();
    msgBHandler.process();
}

运行结果为

检查字段是否为空,对象是否合法等等
设置处理时间等于当前时间,记录到日志
作其余的特别的事情
MsgA 处理业务逻辑

检查字段是否为空,对象是否合法等等
设置处理时间等于当前时间,记录到日志
作其余的特别的事情
MsgB 处理业务逻辑

实现了模版方法的子类(ConcreteMsgAHandler, ConcreteMsgBHandler)在执行了 process 方法以后都会检查字段是否为空,设置处理时间,作其余事情,而后作本身的业务。
process 方法为模板方法,里面的代码块即咱们这里的算法框架,定义了通用的算法,或者说封装了不变的部分。doLogic 方法由子类实现具体的逻辑。

这样若是还有一个消息C(MsgC),咱们只要建立一个 ConcreteMsgCHandler 而且实现 doLogic 方法便可。

扩展和维护起来很是方便。修改通用算法只需修改模板方法,扩展只需新建子类。

在模板方法中还有一个 setDo 方法,这个方法便是模板的方法的钩子函数,若是某个子类不须要通用算法的某个部分,能够经过 setDo 来改变父类通用算法的行为。

public static void main(String[] args) {
    MsgA msgA = new MsgA();
    msgA.setContent("a");

    MsgB msgB = new MsgB();
    msgB.setContent("b");

    ConcreteMsgAHandler msgAHandler = new ConcreteMsgAHandler();
    ConcreteMsgBHandler msgBHandler = new ConcreteMsgBHandler();

    msgAHandler.put(msgA);
    msgBHandler.put(msgB);


    msgAHandler.get();
    msgBHandler.setDo(false);
    msgBHandler.get();
}

输出的结果为

检查字段是否为空,对象是否合法等等
设置处理时间等于当前时间,记录到日志
作其余的特别的事情
MsgA 处理业务逻辑

检查字段是否为空,对象是否合法等等
设置处理时间等于当前时间,记录到日志
MsgB 处理业务逻辑

对比上面的输出就能够发现消息B再也不 作其余的特别的事情

相关文章
相关标签/搜索