设计模式之模板方法模式

栏目: 后端 · 发布时间: 5年前

内容简介:模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤以上的定义可以知道模板方法模式由两部分组成模板方式将是共性的部分放在父类中,不同的部分放在子类中依据不同的情况分别实现。这样的实现方式可以避免重复的行为在各个子类中冗余

模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤

以上的定义可以知道模板方法模式由两部分组成

  1. 抽象的实现算法(抽象类)
  2. 子类的具体实现方法(实现类)

模板方式将是共性的部分放在父类中,不同的部分放在子类中依据不同的情况分别实现。这样的实现方式可以避免重复的行为在各个子类中冗余

二、例子

《Head First设计模式》中讲到coffee or tea这个例子是个经典的模板方法模式,作为一个资深吃货,由于总吃外卖,且在北方生活基本吃干饭,一个南方人,总是对于吃饭喝汤的执着甚深,也愿意自己动手煲汤,发现炖汤做菜也是些符合模板方法模式->_->!! 果然吃货的脑回路就是不一样,哈哈。接下来看下我煲玉米排骨汤和牛肉萝卜汤的的例子:

玉米排骨汤

通常炖玉米排骨汤的步骤是这样的:

  1. 用凉水把排骨焯一遍
  2. 捞出放炖盅,加上生姜、料酒、玉米
  3. 煮一小时
  4. 排骨玉米汤盛碗里

我们用代码大致模拟下这个过程:

let Yumipaigu = function(){}
Yumipaigu.prototype.chaopaigu = function() {
    console.log('排骨焯水');
}
Yumipaigu.prototype.addYumi = function() {
    console.log('加生姜、料酒、玉米');
}
Yumipaigu.prototype.boil = function(){
    console.log('煮一小时');
}
Yumipaigu.prototype.pourInBowl = function() {
    console.log('排骨玉米汤盛碗里');
}
Yumipaigu.prototype.init = function() {
    this.chaopaigu();
    this.addYumi();
    this.boil();
    this.pourInBowl();
}
let yumipaigu =  new Yumipaigu();
yumipaigu.init();
复制代码

至此,一份排骨玉米汤就完成啦!我们看下炖牛肉萝卜汤是怎么样的一个过程:

牛肉萝卜汤

  1. 用凉水把牛肉焯一遍
  2. 捞出放炖盅,加入葱姜蒜、桂皮、香叶、花椒、萝卜
  3. 煮四十分钟
  4. 牛肉萝卜汤盛碗里

我们仍然用代码大致表示下:

let Niurouluobo = function(){}
Niurouluobo.prototype.chaoniurou = function() {
    console.log('牛肉焯水');
}
Niurouluobo.prototype.addNiurou = function() {
    console.log('加入葱姜蒜、桂皮、香叶、花椒、萝卜');
}
Niurouluobo.prototype.boil = function(){
    console.log('煮四十分钟');
}
Niurouluobo.prototype.pourInBowl = function() {
    console.log('牛肉萝卜汤盛碗里');
}
Niurouluobo.prototype.init = function() {
    this.chaoniurou();
    this.addNiurou();
    this.boil();
    this.pourInBowl();
}
let niurouluobo =  new Niurouluobo();
niurouluobo.init();
复制代码

现在我们把两个的过程都用代码大致表示出来了,我们也能从中发现一些两者之间的相似点,我们做个比较与总结

比较与总结

设计模式之模板方法模式

我们看下这两者之前存在着那些不同的地方:

  1. 煮汤的主原料是不一样的,一个是排骨玉米,另一个牛肉萝卜,我们可以统称为这些为"食材"
  2. 煮的过程中添加的调味剂也不大不相同,牛肉萝卜放了好多的香料,我们也统一下,都称之为"辅料"
  3. 煮的过程中,排骨需要煮上一小时,牛肉只需要40分钟,但是动作都是"煮"

分离出了不同之处,那给这两个过程做一个统一:

  1. 原材料焯水
  2. 添加辅料
  3. 煮熟
  4. 盛碗里

发现了吧,是不是很符合我们说的模板方法模式的定义,把上面总结的统一过程作为一个实现做汤的"算法",具体做什么汤,分别实现。现在我们就要运用模板方法来模式实现下前面两个做汤的过程,在此,你可以先忘记上面是如何实现的:

首先,我们先建立一个做汤的父类,实现这个算法:

let MakeSoup = function() {}

MakeSoup.prototype.blanching = function() {}  // 空方法,由子类重写

MakeSoup.prototype.addExcipients = function() {}  // 空方法,由子类重写

MakeSoup.prototype.cooked = function() {}  // 空方法,由子类重写

MakeSoup.prototype. intoBowl = function() {}  // 空方法,由子类重写

MakeSoup.prototype.init = function () {

    this.blanching()

    this.addExcipients()

    this.cooked()

    this.intoBowl()

}
复制代码

现在这个MakeSoup类就算是实现了,但是只有这个类并不能做出什么具体的汤,因为说了,这个父类只是提供了一个抽象的算法步骤,并没有真正的意义,因此我们还要根据具体的内容实现我们所需要的内容

接下来分别实现下排骨玉米汤和牛肉萝卜这两个类:这两个类需要先继承MakeSoup,然后按照里面的步骤一步一步的重写实现

排骨玉米汤类

let Paiguyumi = new function()

Paiguyumi.prototype = new MakeSoup()

Paiguyumi.prototype.blanching = function () {
    console.info("焯排骨")
}

Paiguyumi.prototype.addExcipients = function () {
    console.info("添加生姜、料酒、玉米")
}

Paiguyumi.prototype.cooked = function () {
    console.info("煮一小时")
}

Paiguyumi.prototype.intoBowl = function () {
    console.info("排骨玉米盛碗里")
}

let paiguyumi = new Paiguyumi()

paiguyumi.init()
复制代码

牛肉萝卜

let Niurouluobo = new function()

Niurouluobo.prototype = new MakeSoup()

Niurouluobo.prototype.blanching = function () {
    console.info("焯牛肉")
}

Niurouluobo.prototype.addExcipients = function () {
    console.info("加入葱姜蒜、桂皮、香叶、花椒、萝卜")
}

Niurouluobo.prototype.cooked = function () {
    console.info("煮40分钟")
}

Niurouluobo.prototype.intoBowl = function () {
    console.info("牛肉萝卜盛碗里")
}

let niurouluobo = new Niurouluobo()

niurouluobo.init()
复制代码

目前我们的两个子类都模拟完了,当调用子类的init方法时,因为子类对象和构造器原型prototype上都没有对应的init方法,请求会顺着原型链找到父类的原型上对应的init方法。前面也说过了,模板方法,是封装了子类的实现算法,然后给子类的实现提供指引,告诉子类以什么样的顺序正确实执行哪些方法。在此例子中,MakeSoup.prototype.init自然便是我们的模板方法。

三、关于模板方法模式的一些解释说明

抽象类

1. 何为抽象类

抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。抽象类是不完整的,它只能用作基类。在面向对象方法中,抽象类主要用来进行类型隐藏和充当全局变量的角色。

划重点:

  1. 对一系列看上去不同,但是本质上相同的具体概念的抽象
  2. 只能用作基类,主要用来进行类型隐藏和充当全局变量的角色

对于第一点很好理解吧,上面我们的例子也就说明了这一点,看上去两种做汤方式确实不一样,但是,归纳差异点之后,我们也能抽象出init这个模板方法,来实现本质相同。

第二点,在面向对象的语言中,抽象类是不能被实例化的,继承了某个抽象类,那么它的子类都将拥有跟抽象类一样的接口方法,是为他的子类定义公共接口。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式,即它是在产生子类的同时给予子类一些特定的属性和方法。

2.javascript 模拟缺陷

实际上抽象类在JavaScript语言层面上并没有提供支持,前面我们使用的是原型继承的方式来模拟类继承,也并没有真正意义上的实现,因为在面向对象语言中,当子类继承了某个抽象类时,是必须重写父类的抽象方法,否则编译时不通过,javascript中没有这些检查,因此实现代码的时候需要人为干预,这样的做法是很不安全的,我们可以在父类的抽象方法中直接抛出一个异常:

MakeSoup.prototype.blanching = function() {
    console.error("子类必须重写blanching方法!");
}
复制代码

类似的,每个抽象方法都手动抛出错误。

四、总结

模板方法模式是非常典型的用封装来提高系统拓展性的设计模式,设计了模板方法模式的代码中,子类拥有的属性和方法的执行顺序都是被确定的,在后来的拓展中,我们只要增加新的子类,就可以增加系统的新功能,而无需改动抽象类的代码。在javascript中,模拟模板方法模式固然有时候也不错,但是还有一个可能会是更好的选择,即高阶函数。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

思考的技术

思考的技术

[日]大前研一 / 刘锦秀、谢育容 / 中信出版社 / 2010-11 / 32.00元

思路决定出路,没有了思路,也就没有了出路。 在充满危机与冒险的当下,我们缺乏的不是技巧而是揭发事务本质的动力和好奇心,缺少怀疑一切的心态和对固有模式的怠惰。 大前研一凭借他30多年的管理咨询经验,为我们提供了一种全新的可借鉴的思考方式。 企业和个人惟有改变既有的思考模式,放弃对过去成功经验的迷恋,学习有创意的思考方法,方能找到正确的经营思路。一起来看看 《思考的技术》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具