深入理解JavaScript系列(41):设计模式之模板方法详解
本文深入解析JavaScript中的模板方法模式,通过泡茶与冲咖啡的实例,展示如何定义算法骨架并将可变步骤延迟到子类。文章详细阐述了抽象类的构建、Hook机制的应用以及该模式在控制子类扩展和避免代码重复方面的核心价值,帮助开发者掌握这一基础且重要的设计模式。
介绍
模板方法模式概述
模板方法模式定义了一个操作中算法的骨架,并将部分步骤延迟到子类中执行。通过这种方式,子类可以在不改变算法整体结构的前提下,重新定义某些特定步骤的行为。
作为一种代码复用的基本技术,模板方法在类库设计中尤为重要,因为它能够提取类库中的公共行为。这种模式导致了一种反向的控制结构,即著名的“好莱坞法则”——“别来找我们,我们找你”。这意味着由父类调用子类操作,而非相反。在面向对象编程中,这通常体现为抽象类及其抽象方法,以及继承自抽象类的子类。
正文
实例:泡茶与冲咖啡
以泡茶和冲咖啡为例,两者拥有相同的步骤:烧开水(boilWater)、冲泡(brew)、倒入杯中(pourOnCup)以及添加小料(addCondiments)。尽管主要流程一致,但每种饮料的具体冲泡方式和所加小料不同。利用模板方法模式,我们可以封装这些主要步骤,实现代码复用。
抽象类实现
首先,我们需要定义抽象的步骤和基础逻辑:
代码如下:
var CaffeineBeverage = function () {
};
CaffeineBeverage.prototype.prepareRecipe = function () {
this.boilWater();
this.brew();
this.pourOnCup();
if (this.customerWantsCondiments()) {
// 如果可以想加小料,就加上
this.addCondiments();
}
};
CaffeineBeverage.prototype.boilWater = function () {
console.log("将水烧开!");
};
CaffeineBeverage.prototype.pourOnCup = function () {
console.log("将饮料到再杯子里!");
};
CaffeineBeverage.prototype.brew = function () {
throw new Error("该方法必须重写!");
};
CaffeineBeverage.prototype.addCondiments = function () {
throw new Error("该方法必须重写!");
};
// 默认加上小料
CaffeineBeverage.prototype.customerWantsCondiments = function () {
return true;
};
};
CaffeineBeverage.prototype.prepareRecipe = function () {
this.boilWater();
this.brew();
this.pourOnCup();
if (this.customerWantsCondiments()) {
// 如果可以想加小料,就加上
this.addCondiments();
}
};
CaffeineBeverage.prototype.boilWater = function () {
console.log("将水烧开!");
};
CaffeineBeverage.prototype.pourOnCup = function () {
console.log("将饮料到再杯子里!");
};
CaffeineBeverage.prototype.brew = function () {
throw new Error("该方法必须重写!");
};
CaffeineBeverage.prototype.addCondiments = function () {
throw new Error("该方法必须重写!");
};
// 默认加上小料
CaffeineBeverage.prototype.customerWantsCondiments = function () {
return true;
};
该函数在原型上扩展了所有基础步骤以及主要流程。其中,冲泡(brew)和添加小料(addCondiments)的具体实现在抽象类中未实现,需由具体的饮料子类来完成。此外,是否添加小料(customerWantsCondiments)默认返回true,子类在重写时可以根据需要修改该值。
具体子类实现
下面两个函数分别对应冲咖啡和冲茶的具体实现:
代码如下:
// 冲咖啡
var Coffee = function () {
CaffeineBeverage.apply(this);
};
Coffee.prototype = new CaffeineBeverage();
Coffee.prototype.brew = function () {
console.log("从咖啡机想咖啡倒进去!");
};
Coffee.prototype.addCondiments = function () {
console.log("添加糖和牛奶");
};
Coffee.prototype.customerWantsCondiments = function () {
return confirm("你想添加糖和牛奶吗?");
};
//冲茶叶
var Tea = function () {
CaffeineBeverage.apply(this);
};
Tea.prototype = new CaffeineBeverage();
Tea.prototype.brew = function () {
console.log("泡茶叶!");
};
Tea.prototype.addCondiments = function () {
console.log("添加柠檬!");
};
Tea.prototype.customerWantsCondiments = function () {
return confirm("你想添加柠檬嘛?");
};此外,使用confirm对话框可以让用户自行选择是否添加小料,这种交互方式非常实用。
模式适用场景与对比
总结
模板方法模式适用于以下场景:
1. 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。 2. 各子类中的公共行为应被提取出来并集中到一个公共父类中,以避免代码重复;将不同之处分离为新的操作,最后用一个调用这些新操作的模板方法来替换这些不同的代码。 3. 控制子类扩展。模板方法仅在特定点调用“hook”操作,从而允许在这些点进行扩展。
与策略模式不同,模板方法使用继承来改变算法的一部分,而策略模式使用委托来改变整个算法。
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
JS使用constructor判断数据类型可靠吗
2026-11-05 13:49:41
