狠狠的学 -- 模板方法模式

本文介绍了模板方法模式,它是基于继承的一种设计模式,常用于定义执行步骤相同的场景。文章通过咖啡和茶的制作过程展示了模板方法模式的构成:抽象父类包含通用方法,子类继承并实现特定步骤。还讨论了JavaScript中如何模拟抽象类和接口,以及钩子方法的概念。模板方法模式遵循好莱坞原则,子类只需实现细节,调用顺序由父类控制。最后,通过高阶函数展示了在JS中实现模板方法模式的另一种方式。
摘要由CSDN通过智能技术生成

  JS开发用到继承的地方并不多,模板方法模式是基于继承的一种设计模式。不同的类型可能存在同样的执行步骤,那么就可以将各个步骤抽离出来,定义在一个类中,各个类型再去继承这个类。

  模板方法模式由 2 个部分组成,抽象父类、具体的实现子类。抽象父类包含了子类共有的方法,并且定义了init方法,封装了子类各个方法的执行顺序。具体的实现子类继承自父类,但可以选择重写父类方法,实现自己与其他子类不同的地方。

   父类中,定义了子类完全相同的方法,子类之间不同的方法只实现为空函数,由子类自己重写。

// 咖啡与茶
// 定义 Coffee、Tea
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();

var Tea = function () { };
Tea.prototype.boilWater = function () {
    console.log('把水煮沸');
}
Tea.prototype.steepTeaBag = function () {
    console.log('用沸水浸泡茶叶');
}
Tea.prototype.pourInCup = function() {
    console.log('把茶水倒进杯子');
}
Tea.prototype.addLemon = function() {
    console.log('加柠檬');
}
Tea.prototype.init = function() {
    this.boilWater();
    this.steepTeaBag();
    this.pourInCup();
    this.addLemon();
}
var tea = new Tea();
tea.init();
// 2.理清不同点,抽离共同点
var Beverage = function() {}
Beverage.prototype.boilWater = function() {
    console.log('把水煮沸');
}
Beverage.prototype.brew = function() {}
Beverage.prototype.pourInCup = function() {}
Beverage.prototype.addCondiments = function() {}
Beverage.prototype.init = function() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    this.addCondiments();
}
// 3.定义子类,继承父类,选择重写独属于自己的方法
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('加糖和牛奶');
}
var coffee = new Coffee;
coffee.init();
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();

  抽象类和接口有 2 个作用,向上转型、为子类定义公共接口。向上转型是为了解耦对象与类型,避免类型的强制检查,将不同类型的对象可以相互交换使用。在静态类型语言中,子类必须实现抽象父类的抽象方法,否则无法编译成功。

  抽象类中有 2 种方法,抽象方法、具体方法。比如,咖啡与茶中的brew就是具体方法,其他方法就是抽象方法。

  JS 没有抽象类,也没有类型检查帮助我们检测子类是否重写了抽象方法。对于JS来说,有 2 种解决方案,鸭子类型模拟接口检查、在抽象方法中抛出异常

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');
}

  使用场景有大有小,构建项目框架(HttpServlet有 7 个生命周期方法,还有一个service方法 – 模板方法,规定了生命周期方法的执行顺序)、构建UI组件(一般有 4 个步骤)。

  钩子方法 hook,可以改变模板方法中规定的算法执行顺序。在父类的最容易变化的地方放置钩子,暴露给子类,可由子类控制是否需要挂钩。

// 钩子方法
var Beverage = function() {}
Beverage.prototype.boilWater = function() {
    console.log('把水煮沸');
}
Beverage.prototype.brew = function() {}
Beverage.prototype.pourInCup = function() {}
Beverage.prototype.addCondiments = function() {}
// hook 子类可以重写该方法,决定 “加糖和牛奶” 方法是否执行。
Beverage.prototype.customerWantsCondiments = function() {
    return true;
}
Beverage.prototype.init = function() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    if (this.customerWantsCondiments()) {
        this.addCondiments();
    }
}

var CoffeWithHook = function() {}
CoffeWithHook.prototype = new Beverage;
CoffeWithHook.prototype.brew = function() {
    console.log('用沸水冲泡咖啡');
}
CoffeWithHook.prototype.pourInCup = function() {
    console.log('将咖啡倒进杯子');
}
CoffeWithHook.prototype.addCondiments = function() {
    console.log('加糖和牛奶');
}
CoffeWithHook.prototype.customerWantsCondiments = function() {
    return window.confirm('请问需要调料吗');
}
var coffeeWithHook = new CoffeWithHook;
coffeeWithHook.init();

  模板方法模式体现了一个原则 – 好莱坞原则发布-订阅、回调同样体现了这一原则。好莱坞原则里有 2 个角色,简单描述为上下级,下级由上级控制调用,下级只负责细节上的实现。映射到模板方法模式就是子类放弃调用的控制权,交由父类控制,只负责细节实现部分。

  之前的例子都是在形式上模拟继承,在JS中可以使用高阶函数实现。

const Beverage = function(param) {
    const boilWater = function() {
        console.log('把水煮沸');
    }
    const brew = param.brew || function() {
        console.log('必须传递 brew 方法');
    }
    const pourInCup = param.pourInCup || function() {
        console.log('必须传递 pourInCup 方法');
    }
    const addCondiments = param.addCondiments || function() {
        console.log('必须传递 addCondiments');
    }
    const F = function() {}
    F.prototype.init = function() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }
    return F;
};

const Coffee = Beverage({
    brew() {
        console.log('用沸水冲泡咖啡');
    },
    pourInCup() {
        console.log('将咖啡倒进杯子');
    },
    addCondiments() {
        console.log('加糖和牛奶');
    }
});
const coffee = new Coffee;
coffee.init();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值