设计模式是什么鬼(模板方法)

作者:凸凹里歐

来源:轻文社 微信号:Todd-Leo

面向对象,是对事物属性与行为的封装,方法,指的就是行为。模板方法,显而易见是说某个方法充当了模板的作用,其充分利用了抽象类虚实结合的特性,虚部抽象预留,实部固定延续,以达到将某种固有行为延续至子类的目的。反观接口,则达不到这种目的。要搞明白模板方法,首先我们从接口与抽象类的区别切入,这也是面试官经常会问到的问题。

image

汽车上的接口最常见的就是这几个了,点烟器,USB,AUX等等,很明显这些都是接口,它们都预留了某种标准,暴露在系统外部,并与外设对接。就拿点烟器接口来说吧,它原本是专门用于给点烟器供电的,后来由于这个接口在汽车上的通用性,于是衍生出了各种外部设备,只要是符合这个标准size的,带正负极簧片的,直流12V的,那就可以使用,比如导航、行车记录仪、吸尘器什么的,以及其他各种车载电子设备。

public interface CigarLighterInterface {//点烟器接口
    //供电方法,16V直流电
    public void electrifyDC16V();
}
public class GPS implements CigarLighterInterface {
    //导航的实现
    @Override
    public void electrifyDC16V() {
        System.out.println("连接卫星");
        System.out.println("定位。。。");
    }
}
public class CigarLighter implements CigarLighterInterface {
    //点烟器的实现
    @Override
    public void electrifyDC16V() {
        int time = 1000;
        while(--time>0){
            System.out.println("加热电炉丝");
        }
        System.out.println("点烟器弹出");
    }
}

对于点烟器接口来说,它根本不在乎也不知道对接的外设是什么鬼,它只是定义了一种规范,一种标准,只要符合的都可以对接。再比如USB接口的应用更加广泛,外设更是应有尽有,具体例子可以参考文章《设计模式是什么鬼(初探)》。

以上我们可以体会到接口的抽象是淋漓尽致的,实现是空无的,也就是说其方法都是无实现的。然而这样在某些场景下会存在一些问题,例如有时候我们在父类中只需抽象出一些方法,并且同时也有一些实体方法,以供子类直接继承,这怎么办?答案就是抽象类。举个例子,哺乳动物类,我们人类就是哺乳动物。

image

什么?鲸鱼是哺乳类?是的,凡是喂奶的都是哺乳类,不要以为会游泳的都是鱼,会飞的都是鸟,蝙蝠同样是哺乳类,只不过是老鼠中的飞行员而已。

image

既然如此这哺乳动物肯定是都能喂奶了,但是到底是跑还是游,或是飞呢还真不好说,但至少可以确认它们都是可以移动的。言归正传,我们开始定义哺乳动物抽象类。

public abstract class Mammal {

    //既然是哺乳动物当然会喂奶了,但这里约束为只能母的喂奶
    protected final void feedMilk(){
        if(female){//如果是母的……
          System.out.println("喂奶");
        }else{//如果是公的……或者可以抛个异常出去。
            System.out.println("公的不会");
        }
    }

    //哺乳动物当然可以移动,但具体怎么移动还不知道。
    public abstract void move();
}

这里我们省略了female属性,其作用是为了控制喂奶行为,大家可以自行添加。可以看到的是,抽象类不同于接口,其自身是可以有具体实现的,也就是说抽象类是虚实结合的,虚部抽象行为,实部遗传给子类,虚虚实实,飘忽不定。OK,我们看下人、鲸、蝠的子类实现。

public class Human extends Mammal {

    @Override
    public void move() {
        System.out.println("两条腿走路……");
    }

}
public class Whale extends Mammal {

    @Override
    public void move() {
        System.out.println("游泳……");
    }

}
public class Bat extends Mammal {

    @Override
    public void move() {
        System.out.println("用翅膀飞……");
    }

}

可以看到子类的各路实现都是自己独有的行为方式,而喂奶那个行为是不需要自己实现的,它是属于抽象哺乳类的共有行为,哺乳子类不能进行任何干涉。这便是接口与抽象的最大区别了,接口是虚的,抽象类可以有虚有实,接口不在乎实现类是什么,抽象类会延续其基因给子类。

其实到这里我们已经说了一大半了,理解了以上部分,剩下的部分就非常简单了,利用抽象类的这个特性,便有了“模板方法”。举例说明,我们做软件项目管理,按瀑布式简单来讲分为:需求分析、设计、编码、测试、发布,先不管是用何种方式去实现各个细节,我们就抽象成这五个步骤。

public abstract class PM {
    protected abstract void analyze();//需求分析
    protected abstract void design();//设计
    protected abstract void develop();//开发
    protected abstract boolean test();//测试
    protected abstract void release();//发布
}

那么问题来了,有个程序员在需求不明确或者设计不完善的情况下,一上来二话不说直接写代码,这样就会造成资源的浪费,后期改动太大还会影响项目进度。那么项目经理这时就应该规范一下这个任务流程,这里我们加入kickoff()方法实现。

public abstract class PM {
    protected abstract void analyze();//需求分析
    protected abstract void design();//设计
    protected abstract void develop();//开发
    protected abstract boolean test();//测试
    protected abstract void release();//发布

    protected final void kickoff(){
        analyze();
        design();
        develop();
        test();
        release();
    }
}

这样就限制了整个项目周期的任务流程,注意这里要用final声明此方法子类不可以重写,只能乖乖的继承下去用。至于其他的抽象方法,子类可以自由发挥,比如测试方法test(),子类可以用人工测试,自动化测试,我们不care,我们是站在项目管理的抽象高度,只把控流程进度。这里甚至我们还可以加入一些逻辑如下。

public abstract class PM {
    protected abstract void analyze();//需求分析
    protected abstract void design();//设计
    protected abstract void develop();//开发
    protected abstract boolean test();//测试
    protected abstract void release();//发布

    protected final void kickoff(){
        analyze();
        design();
        do {
            develop();
        } while (!test());//如果测试失败,则继续开发改Bug。
        release();
    }
}

以下子类只需实现抽象方法,而不用实现固有的模板方法kickoff(),因为它已经被父类PM实现了,并且子类也不能进行重写。

public class AutoTestPM extends PM{

    @Override
    protected void analyze() {
        System.out.println("进行业务沟通,需求分析");     
    }

    //design();develop();test();release();实现省略
}

至此,我们的模板方法就完成了,抽象类PM中的实方法kickoff()中,以某种逻辑编排调用了其他各个抽象方法,提供了一种固定模式的行为方式或是指导方针,以此达到虚实结合、柔中带刚、刚柔并济,灵活中不失规范的目的。

image

当然大部分情况我们使用接口会多于抽象类,因为接口灵活啊,抽象类不允许多继承啊等等,其实我们还是要看应用场景,在某种无规矩不成方圆,或者规范比较明确,的情况下抽象类的应用是有必要的,世间万物没有最好的,只有最合适的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容