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

概念

  模板方法模式是一种只需使用继承就可以实现的非常简单的模式。模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
  假如有一些平行的子类,各个子类之间有一些相同的行为,也有一些不同的行为。如果相同和不同的行为都混合在各个子类的实现中,说明这些相同的行为会在各个子类中重复出现。但实际上,相同的行为可以被搬移到另外一个单一的地方,模板方法模式就是为解决这个问题而生的。在模板方法模式中,子类实现中的相同部分被上移到父类中,而将不同的部分留在子类来实现。这也很好地体现了泛化的思想。

应用

抽象类

  模板方法模式是一种严重依赖抽象类的设计模式。JS在语言层面并没有提供对抽象类的支持,故下面代码用JS的超集TS编写。
  我们先来看看抽象类在正统的面向对象编程语言中的作用。在Java中,类分为两种,一种为具体类,另一种为抽象类。具体类可以被实例化,抽象类不能被实例化。要了解抽象类不能被实例化的原因,可以思考“饮料”这个抽象类。
  想象这样一个场景:口渴了去便利店想买一瓶饮料,不能直接跟店员说:“来一瓶饮料”。如果这样说了,那么店员接下来肯定会问:“要什么饮料?”饮料只是一个抽象名词,只有当真正明确了的饮料类型之后,才能得到一杯咖啡、茶或者可乐。
  由于抽象类不能被实例化,如果有人编写了一个抽象类,那么这个抽象类一定是用来被某些具体类继承的。抽象类表示一种契约。继承了这个抽象类的所有子类都将拥有跟抽象类一致的接口方法,抽象类的主要作用就是为它的子类定义这些公共接口。如果在子类中删掉了这些方法中的某一个,那么将不能通过编译器的检查。
  抽象方法被声明在抽象类中,抽象方法并没有具体的实现过程,是一些“哑”方法。除了抽象方法之外,如果每个子类中都有一些同样的具体实现方法,那这些方法也可以选择放在抽象类中,这可以节省代码以达到复用的效果,这些方法叫作具体方法。
  咖啡与茶是一个经典的例子,经常用来讲解模板方法模式,这个例子的原型来自《HeadFirst设计模式》。下面用TS模板方法模式来实现这个例子。

public abstract class Beverage{    //饮料抽象类
    init():void{    //模板方法
        this.boilWater()
        this.brew()
        this.pourInCup()
        this.addCondiments()
    }

    boilWater():void{    //具体方法
       console.log("把水煮沸")
    }

    abstract  brew():void    //抽象方法brew
    abstract  addCondiments():void        //抽象方法addCondiments
    abstract pourInCup():void    //抽象方法pourInCup
}

public class Coffee extends Beverage{    //Coffee类
     brew():void{    //子类中重写brew方法
        console.log("用沸水冲泡咖啡")
    }
     pourInCup():void{    //子类中重写pourInCup方法
        console.log("把咖啡倒进杯子")
    }
     addCondiments():void{    //子类中重写addCondiments方法
        console.log("加糖和牛奶")
    }
}

public class Tea extends Beverage{    //Tea类
    brew():void{    //子类中重写brew方法
        console.log("用沸水浸泡茶叶")
    }

    pourInCup():void{    //子类中重写pourInCup方法
        console.log("把茶倒进杯子")
    }

    addCondiments():void{    //子类中重写addCondiments方法
        console.log("加柠檬")
    }
}
Beverage coffee = new Coffee()    // 创建coffee对象
coffee.init()     // 把水煮沸、用沸水冲泡咖啡、把咖啡倒进杯子、加糖和牛奶
Beverage tea = new Tea()    // 创建tea对象
tea.init()       // 把水煮沸、用沸水浸泡茶叶、把茶倒进杯子、加柠檬

钩子方法

  放置钩子是隔离变化的一种常见手段。在父类中容易变化的地方放置钩子,钩子可以有一个默认的实现,究竟要不要“挂钩”,这由子类自行决定。钩子方法的返回结果决定了模板方法后面部分的执行步骤,也就是程序接下来的走向,这样一来,程序就拥有了变化的可能。

 class Beverage {
    init(): void {
        this.boilWater()
        this.brew()
        this.pourInCup()
        if (this.customerWantsCondiments() ){ // 如果挂钩返回true,则需要调料
            this.addCondiments();
        }
    }

    boilWater(): void {
        console.log("把水煮沸")
    }

    brew() {
        throw new Error('子类必须重写brew方法')
    }

    pourInCup() {
        throw new Error('子类必须重写pourInCup方法')
    }

    customerWantsCondiments ():boolean {
        return true
     }
     addCondiments() {
         throw new Error('子类必须重写addCondiments方法')
     }
}

 class Coffee extends Beverage {
    brew() {
        console.log("用沸水冲泡咖啡")
    }

    pourInCup(){
        console.log("把咖啡倒进杯子")
    }

     customerWantsCondiments() {
         return window.confirm( '请问需要调料吗?' );
    }
     addCondiments() {
         console.log( '加糖和牛奶' );
     }
}

Coffee coffee = new Coffee()
coffee.init()

  下面引入一个新的设计原则——“好莱坞原则”。好莱坞无疑是演员的天堂,但好莱坞也有很多找不到工作的新人演员,许多新人演员在好莱坞把简历递给演艺公司之后就只有回家等待电话。有时候该演员等得不耐烦了,给演艺公司打电话询问情况,演艺公司往往这样回答:“不要来找我,我会给你打电话。”
  在设计中,这样的规则就称为好莱坞原则。在这一原则的指导下,允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件,高层组件对待底层组件的方式,跟演艺公司对待新人演员一样,都是“别调用我们,我们会调用你”
  模板方法模式是好莱坞原则的一个典型使用场景,它与好莱坞原则的联系非常明显,用模板方法模式编写一个程序时,就意味着子类放弃了对自己的控制权,而是改为父类通知子类,哪些方法应该在什么时候被调用。作为子类,只负责提供一些设计上的细节。

小结

  模板方法模式是一种典型的通过封装变化提高系统扩展性的设计模式。在传统的面向对象语言中,一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以把这部分逻辑抽象到父类的模板方法里面。而子类的方法具体怎么实现则是可变的,于是把这部分变化的逻辑封装到子类中。通过增加新的子类,便能给系统增加新的功能,并不需要改动抽象父类以及其他子类,这也是符合开放——封闭原则的。

参考文献

《JavaScript设计模式与开发实践》

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

推荐阅读更多精彩内容