聊聊设计模式 — 模板方法模式

来源:互联网 发布:修改telnet端口 编辑:程序博客网 时间:2024/06/05 14:55

原帖地址:http://blog.csdn.net/roderick2015/article/details/52613886,转载请注明。

    模板方法模式概括起来,就是先定义一个父类,然后写个模板方法包含一套处理流程,流程中的每个步骤都对应到独立的方法中处理,最后在模板方法中按照顺序汇总执行。流程可以这样理解,比如我们把下班回家吃饭分成三个步骤:1.下班回家 2.做饭 3.吃饭,这就是一个流程,而且必须得按照逻辑顺序,你肯定不能先吃饭,再做饭吧。而人千千万,是不可能一模一样的,有人开车回家,有人挤地铁回家,这就是子类要做的事,覆写父类的步骤方法,给出自己的实现。接着第二步,有的人啊比较幸福,回到家后老婆已经把饭做好了,是不是就不用做饭了,所以在这个子类中,做饭步骤不用执行,这就引出了钩子方法 — 由子类的条件决定父类是否执行某个步骤。最后吃饭,假定我们都是用筷子吃饭的话,属于相同的实现,父类就可以把吃饭这个步骤写成公共方法给子类直接调用,于是避免了重复代码。

    以上内容只是为了帮助读者对模板方法模式有一个整体性的理解,接下来才是正餐~


     我们正式开聊设计模式中的模板方法模式,在实际项目中使用的频率是非常高的,想必就算不了解也应该有见过,首先我会举例一个需求场景,再逐步引出模式代码。

    话说包工头老王最近承包了XX公司的自行车车棚项目,所以他请了两位搬砖工来帮他运砖头。第二天一早,搬砖工A到老王这签到(其实就是打个招呼),然后满怀期待的等着任务。老A啊,你赶紧去把南边广场的那堆砖搬过来吧,昨晚都被大妈投诉了,说影响她们排练节目,这事可一定不能耽搁了,今天必须搬完。好勒,老A这就去了。过了会搬砖工B也来了,老王一看这伙计真结实,乐了,哈哈好,那你就辛苦一下,把北边职工宿舍的砖头搬过来吧,数量有点多,到时候给你这个数。

    事情的经过就是这样的,我们先讲到这,开始敲代码(码砖)了,先实现最最最初级的第一版代码,如下所示。

/** * 搬砖工A */public class HimmaA {    public void moveBricks(String task, double money) {        System.out.println("HimmaA来签到");        System.out.println("HimmaA接受任务:" + task);        System.out.println("HimmaA跑去搬" + task);        System.out.println("活干完了,拿到工钱" + money + "元");    }}/** * 搬砖工B */public class HimmaB {    public void moveBricks(String task, double money) {        System.out.println("HimmaB来签到");        System.out.println("HimmaB接受任务:" + task);        System.out.println("HimmaB跑去搬" + task);        System.out.println("活干完了,拿到工钱" + money + "元");    }}/** * 包工头 */ public class Foreman {    public static void main(String[] args) {        HimmaA a = new HimmaA();        a.moveBricks("南边广场的砖", 236.3);        HimmaB b = new HimmaB();        b.moveBricks("北边宿舍的一大堆砖", 356.2);    }}

    代码这就写完了,是不是够直接够粗暴,在很短的时间内就完成了我们的需求。但是这样的代码问题也显而易见:代码重复,耦合性高,毫无扩展性,要是需求稍稍一改,又得折腾半天。
    我们总结一下,搬砖工一共要干四件事,签到、接任务、搬砖、拿赏钱,那可以放到独立的方法里分成四个步骤,这样逻辑清晰,互不影响,修改后的HimmaA代码如下所示。

/** * 搬砖工A */public class HimmaA {    private String task = "";    public void sign() { System.out.println("HimmaA来签到"); }    protected void receiveTask(String task) {        this.task = task;         System.out.println("HimmaA接受任务:" + task);     }    protected void doWork() { System.out.println("HimmaA跑去搬" + this.task); }    protected void getPay(double money) { System.out.println("活干完了,拿到工钱" + money + "元"); }    public void moveBricks(String task, double money) {        sign();        receiveTask(task);        doWork();        getPay(money);    }}

    我们又发现class HimmaA和HimmaB 的步骤其实是一样的,只是干的事情不同,那我是不是可以把他们提取出来,放到他们的公共父类里,由父类来定义这些步骤,而且得是抽象类,万一你把它当成了真正的搬砖工拿去搬砖,那怎么行。修改后的第二版代码如下所示。

/** * 我不是搬砖工 */public abstract class AbsHimma {    private double money = 0;    protected abstract void sign();    protected abstract void receiveTask(String task);    protected abstract void doWork();    protected void getPay(double money) { System.out.println("活干完了,拿到工钱" + money + "元"); }    final public void moveBricks(String task) { //声明成final,子类别乱来        sign();        receiveTask(task);        doWork();        getPay(this.money);    }    //money改为setter方法注入    public void setMoney(double money) {        this.money = money;    }}

    我在父类的moveBricks方法中定义了整个搬砖的流程:签到sign,接任务receiveTask,干活doWork,领取报酬getPay,搬砖工A和搬砖工B领钱的代码是一样的,所以编写了具体的实现代码。另外三个只提供抽象方法,由子类负责具体的实现。接着我们看修改后的子类代码,如下所示。

/** * 搬砖工A */public class HimmaA extends AbsHimma {    private String task = "";    @Override    public void sign() { System.out.println("HimmaA来签到"); }    @Override    protected void receiveTask(String task) {        this.task = task;        System.out.println("HimmaA接受任务:" + task);    }    @Override    protected void doWork() { System.out.println("HimmaA跑去搬" + task); }}/** * 搬砖工B */public class HimmaB extends AbsHimma {    private String task = "";    @Override    public void sign() { System.out.println("HimmaB来签到"); }    @Override    protected void receiveTask(String task) {        this.task = task;        System.out.println("HimmaB接受任务:" + task);    }    @Override    protected void doWork() { System.out.println("HimmaB跑去搬" + task); }}

    最后看下包工头的代码修改如下所示。

/** * 包工头 */public class Foreman {    public static void main(String[] args) {        AbsHimma a = new HimmaA();        a.setMoney(236.3);        a.moveBricks("南边广场的砖");        System.out.println("-----------分割线-----------");        AbsHimma b = new HimmaB();        b.setMoney(356.2);        b.moveBricks("北边宿舍的一大堆砖");    }}

    最后看下运行结果,如下图所示。
这里写图片描述

    好了,一个由父类提供模板流程,子类提供具体实现的代码就已经写好了,它分离了流程定义与步骤的实现,而且具有很好的扩展性,接下来就搬出我们的大定义。

    模板方法模式:定义一个算法框架,并将一些步骤延迟到子类中实现。使子类能在不改变算法结构的情况下,重新定义该算法中某些特定步骤的实现。
    类图如下图所示。
这里写图片描述

    其中templateMethod就是模板方法,doAnything和doSomething方法是基本方法,用于子类实现,并在模板方法中调用。

模式的扩展

    事情还没完,搬砖工B在回家的路上,恰巧碰到隔壁工棚的C,交流了下当天的 生(你) 活(赚) 经(多) 历(少) ,最后B发现干了一大堆活,工头只给这么点,明显低于行价,这是剥削啊,于是B闷闷不乐,估计明天得找包工头理论或者干脆跟着C一起混了。

    那这个需求怎么实现呢,我们来分析一下,B多了一个步骤,就是下班后交流经验并发表感慨,所以得修改我们的模板方法,但是搬砖工A人家老老实实的就回家了啊,没这么多事,根本不需要这个步骤,况且B也不是每天都交流经验,得,本来一套流程就顺利解决的问题,现在得两套流程了?

    先别急着分流程,B除了这个步骤外,其他还是一样的嘛,那就给他开个特例,专门为B额外增加一个步骤。另外从Open Closed原则角度出发,更不应修改原有代码,而是通过新增和扩展来实现所需的功能,因此我们可以在父类中新增条件判断的方法并由子类通过扩展来决定结果,修改后的第三版代码如下所示。

public abstract class AbsHimma {    protected double money = 0;    protected abstract void sign();    protected abstract void receiveTask(String task);    protected void doWork() {        throw new UnsupportedOperationException();    }    protected void getPay(double money) { System.out.println("活干完了,拿到工钱" + money + "元"); }    protected boolean hasEmotion() { return false; } //条件判断    protected void releaseEmotion() { } //需要发牢骚的请覆写该方法    final public void moveBricks(String task) {        sign();        receiveTask(task);        doWork();        getPay(this.money);        if (hasEmotion()) releaseEmotion(); //新增步骤    }    public void setMoney(double money) {        this.money = money;    }}

    再看下修改后的HimmaB类

/** * 搬砖工B */public class HimmaB extends AbsHimma {    private String task = "";    @Override    public void sign() { System.out.println("HimmaB来签到"); }    @Override    protected void receiveTask(String task) {        this.task = task;        System.out.println("HimmaB接受任务:" + task);    }    @Override    protected void doWork() { System.out.println("HimmaB跑去搬" + task); }    @Override    protected boolean hasEmotion() {        if (this.money < 500) {            return true; //低于行价,我有话要说        } else            return false;    }    @Override    protected void releaseEmotion() {        System.out.println("HimmaB吃了亏,很生气,明天找老王理论!");    }}

    运行结果如下图所示。
这里写图片描述

    这就是模板方法模式的一个重要扩展——钩子方法(Hook Method),AbsHimma类的hasEmotion方法就是钩子方法,通过该方法就可以实现,由子类方法的返回值决定公共流程moveBricks的运行结果,甚至改变它的执行顺序。

优点

    模板方法模式到这就讲完了,总的来说有三个好处:
    1.将不变的部分封装到模板方法,可变部分由子类扩展各自实现。比如这时候又来了个搬砖工C,那只需要继承公共父类,并给出自己的实现方式即可,不影响原有代码。如需要修改模板方法,则通过钩子方法进行适当变通。
    2.行为与实现分离,这样可以使代码具有清晰的行为逻辑,并降低代码间的耦合度。
    3.提取公共代码,减少重复,对日后的代码维护者是非常友好的,对于细微的功能变动需求, 完全不是事。

缺陷

    当然在实际项目中模板方法模式的缺陷还是蛮明显的,你想父类影响子类,子类还可以反过来决定父类,当逻辑越来越复杂,牵涉的东西很多时,很容易就绕来绕去了,理解起来比较困难,这时候就需要我们对其进行拆解重构了。

应用场景

    这个模式的使用场景也是非常多的,比如很多开源框架就特别喜欢这样写,自己定义好流程,给用户开个口子,让我们自己加点扩展啥的。然后就是在项目重构的时候,也会经常用到,提取共性再用钩子方法区分,在需要的时候还会进一步抽象并接口化等等,毕竟项目中各模块主要是通过接口耦合的,比如我们可以为搬砖工提取一个Himma接口,具体不展开了,详情可以点击此处查看。记住模式不是目的,别太死板哦。

0 0
原创粉丝点击