JavaScript-设计模式-模板方法模式实例
1 模板方法模式是什么?
模板方法模式:只需要使用集成就能实现。由两部分组成:抽象父类 + 具体的实现子类。
抽象父类:封装子类的算法框架,包括实现一些公用方法以及封装在子类中所有方法的执行顺序
实现子类:通过集成这个抽象类,也继承了整个算法,并且可以选择重写父类的方法
假如我们有许多平行的类,各个类之间有许多相同的行为,也有部分不同的行为。如果各位都定义自己所有的行为,那么会出现很多重复的方法。此时可以将相同的行为搬移到另外一个单一的地方,模板方法模式就是为了解决这个问题。在模板方法模式中,子类中相同的行为被移动到了父类中,而将不同的部分留待子类来实现。
下面将具体展示对应的实例
一杯咖啡
首先我们先来泡一杯咖啡,一般来说,泡咖啡的步骤通常如下:
1.先把水煮沸;
2.用沸水冲泡咖啡;
3.把咖啡倒进杯子;
4.加糖和牛奶。
我们用es5来得到一杯香浓的咖啡吧:
var Coffee=function(){} Coffee.prototype.boilWater=function(){ console.log('水煮开了'); } Coffee.prototype.brewCoffeeGriends=function(){ console.log('用沸水冲泡咖啡'); } Coffee.prototype.pourInCup=function(){ console.log('把咖啡倒进杯子'); } Coffee.prototype.addSugarAndMilk=function(){ console.log('加糖和牛奶'); } // 封装 将实现的细节交给类的内部 Coffee.prototype.init = function() { this.boilWater(); this.brewCoffeeGriends(); this.pourInCup(); this.addSugarAndMilk(); } var coffee=new Coffee(); coffee.init();
如我们所愿了,控制台会输出泡茶的流程,和我们写下的一样。
我想喝茶吖,想喝茶。不急,不急,我们再来泡茶哈! 泡一壶茶啦!
其实呢,泡茶的步骤跟泡咖啡的步骤相差不大,大致是这样的:
1.把水煮沸; 2.用沸水浸泡茶叶; 3.把茶水倒进杯子; 4.加柠檬。
来,咱用es6来泡茶:
class Tea{ constructor(){ } boilWater(){ console.log('把水烧开'); } steepTeaBag(){ console.log('浸泡茶叶'); } pourInCup(){ console.log('倒进杯子'); } addLemon(){ console.log('加柠檬'); } init(){ this.boilWater(); this.steepTeaBag(); this.pourInCup(); this.addLemon(); } } var tea=new Tea(); tea.init();
又如我们所愿了,控制台输出了泡茶的流程。
思考啦!
现在到了思考的时间,我们刚刚泡了一杯咖啡和一壶茶,有没有觉得这两个过程是大同小异的。我们能很容易的就找出他们的共同点,不同点就是原料不同嘛,茶和咖啡,我们可以把他们抽象为"饮料"哇;泡的方式不同嘛,一个是冲泡,一个是浸泡,我们可以把这个行为抽象为"泡";加入的调料也不同咯,加糖和牛奶,加柠檬,它们也可以抽象为"调料"吖。
这么一分析,是不是很清楚了吖,我们整理一下就是:
1.把水煮沸;
2.用沸水冲泡饮料;
3.把饮料倒进杯子;
4.加调料。
抽象父类
由于javascript没有类型检查,我们需要让子类必须实现brew, pourInup和addCondiments,因此这里通过抛出异常来提醒编写者。
var Beverage = function() { }; Beverage.prototype.boilWater = function() { console.log('煮沸水'); }; Beverage.prototype.brew = function(){ throw new Error( '子类必须重写 brew 方法' ); }; // 空方法,应该由子类重写 Beverage.prototype.pourInCup = function(){ throw new Error( '子类必须重写 pourInCup 方法' ); }; // 空方法,应该由子类重写 Beverage.prototype.addCondiments = function(){ throw new Error( '子类必须重写 addCondiments 方法' ); }; // 空方法,应该由子类重写 Beverage.prototype.init = function() { this.boilWater(); this.brew(); this.pourInCup(); this.addCondiments(); }
创建Coffee子类
接下来要重写抽象父类中的一些方法。只要把水煮沸这个行为可以直接使用父类。
var Coffee = function() { }; Coffee.prototype = new Beverage(); Coffee.prototype.brew = function(){ console.log( '用沸水冲泡咖啡' ); }; Coffee.prototype.pourInCup = function(){ console.log( '把咖啡倒进杯子' ); }; Coffee.prototype.addCondiments = function(){ console.log( '加糖和牛奶' ); }; // 当调用init方法时,会找到父类的init方法进行调用。 var coffee = new Coffee(); coffee.init();
创建Tea子类
var Tea = function() { }; Tea.prototype = new Beverage(); Tea.prototype.brew = function(){ console.log( '用沸水浸泡茶叶' ); }; Tea.prototype.pourInCup = function(){ console.log( '把茶倒进杯子' ); }; Tea.prototype.addCondiments = function(){ console.log( '加柠檬' ); }; var tea = new Tea(); tea.init();
模板方法
上面的例子,tea和Coffee都继承了Beverage,那么谁是模板方法呢?Beverage.prototype.init就是模板方法。因为它内部封装了子类的算法框架,它作为一个算法的模板,知道子类以何种顺序执行哪些方法。
2 钩子方法
平时遇到正常的,喝咖啡的都是上面的顺序,但是如果有些人不喜欢加调料,那么上面的步骤又不符合情况了,此时可以通过钩子函数来进行解决。钩子函数通过用户返回的结果来决定接下来的步骤。究竟要不要钩子由子类自己决定。
var Beverage = function() { }; Beverage.prototype.boilWater = function() { console.log('煮沸水'); }; Beverage.prototype.brew = function(){ throw new Error( '子类必须重写 brew 方法' ); }; .... // 钩子函数 Beverage.prototype.customerWantsCondiments = function(){ return true; }; Beverage.prototype.init = function() { this.boilWater(); this.brew(); this.pourInCup(); // 如果钩子函数返回true,则添加调料 if (this,customerWantsCondiments()) { this.addCondiments(); } }
在子类Coffee中,需要实现钩子函数
var Coffee = function() { }; Coffee.prototype = new Beverage(); Coffee.prototype.brew = function(){ console.log( '用沸水冲泡咖啡' ); }; ... // 重写钩子函数 Coffee.prototype.customerWantsCondiments = function(){ return window.confirm('请问需要调料吗?'); }; // 当调用init方法时,会找到父类的init方法进行调用。 var coffee = new Coffee(); coffee.init();
3 真的需要继承吗
模板方法模式就是基于继承的一种设计模式,父类中封装了子类的算法框架和执行顺序,子类继承父类后,父类通知子类执行这些方法。但是javascript并没有提供真正的类式继承,继承是通过对象与对象之间的委托来实现的,也就是形式上借鉴了提供类式的语言。下面这段代码能够达到一样的继承效果。
var Beverage = function( param ){ var boilWater = function(){ console.log( '把水煮沸' ); }; var brew = param.brew || function(){ throw new Error( '必须传递 brew 方法' ); }; var pourInCup = param.pourInCup || function(){ throw new Error( '必须传递 pourInCup 方法' ); }; var addCondiments = param.addCondiments || function(){ throw new Error( '必须传递 addCondiments 方法' ); }; var F = function(){}; F.prototype.init = function(){ boilWater(); brew(); pourInCup(); addCondiments(); }; return F; }; var Coffee = Beverage({ brew: function(){ console.log( '用沸水冲泡咖啡' ); }, pourInCup: function(){ console.log( '把咖啡倒进杯子' ); }, addCondiments: function(){ console.log( '加糖和牛奶' ); } });
小结
模板方法模式在传统的编程语言中,子类的方法种类以及执行顺序都是不变的,这部分逻辑我们都抽象到了父类中,而子类的方法具体怎么实现是可变的,通过重写父类的方法,将变化的逻辑部分封装到子类中。通过增加新的子类,我们能够给系统增加新的功能的,俺是并不需要修改父类以及其他的子类,这也符合开放-封闭原则。在javascript中,我们不需要依样画瓢去实现一个模板方法模式,因为高阶函数是一个更好的选择。
原文链接: https://www.yukx.com/xiaomengbao/article/details/2370.html 优科学习网JavaScript-设计模式-模板方法模式实例
-
在HTML中,如果你想让一个输入框(input元素)不可编辑,你可以通过设置其readonly属性来实现。示例如下:input type="text" value="此处内容不可编辑" readonly在上述代码中,readonly属性使得用户无法修改输入框中的内容。另外,如果你希望输入框完全不可交
-
ASP.NET教程ASP.NET又称为ASP+,基于.NETFramework的Web开发平台,是微软公司推出的新一代脚本语言。ASP.NET是一个使用HTML、CSS、JavaScript和服务器脚本创建网页和网站的开发框架。ASP.NET支持三种不一样的开发模式:WebPages(Web页面)、
-
C# 判断判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。下面是大多数编程语言中典型的判断结构的通常形式:判断语句C#提供了以下类型的判断语句。点击链接查看每个语句的细节。语句描述if语句一个 if语句 由一个布尔表达式后跟
-
C#循环有的时候,可能需要多次执行同一块代码。通常情况下,语句是顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。编程语言提供了允许更为复杂的执行路径的多种控制结构。循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的通常形式:循环类型C#提供了以下几种循环类型
-
C#数组(Array)数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,一般认为数组是一个同一类型变量的集合。声明数组变量并不是声明number0、number1、...、number99一个个单独的变量,而是声明一个就像numbers这样的变量,然后使用numbers[0]
-
ASP.NET是一个由微软公司开发的用于构建Web应用程序的框架,它是.NETFramework的一部分。它提供了一种模型-视图-控制器(MVC)架构、Web表单以及最新的ASP.NETCore中的RazorPages等多种开发模式,可以用来创建动态网页和Web服务。以下是一些基础的ASP.NET编