书中将 Template Method 模板方法模式、Factory Method 工厂方法模式归入类的继承相关的设计模式。
- 模板方法模式:将具体处理交给子类,核心是定义好模板方法,将此方法中调用的某些方法推迟到子类中实现。
- 工厂方法模式:将实例的生成交给子类,核心是定义好工厂方法,将此方法中生成实例的方法和某些方法推迟到子类中实现。
Template Method 模板方法模式
定义
在父类中定义处理流程的框架,在子类中实现具体处理的模式就称为 Template Method 模式。
角色
在模板方法中只有两个角色。
- 抽象类:负责实现模板方法(此模板方法是最终产品的 API),同时负责定义组成此方法的抽象方法。
- 具体类:负责实现上述抽象方法。
JavaScript 实现
// 抽象类
class Login {
init (options) {
// 模板方法
this.initDom(options)
this.initEvent()
this.inited()
}
initDom () {
throw new Error('需实现 LoginTemplate 类的 initDom 方法')
}
initEvent () {
throw new Error('需实现 LoginTemplate 类的 initEvent 方法')
}
inited () {
throw new Error('需实现 LoginTemplate 类的 inited 方法')
}
}
// 具体类
class UsernameLogin extends Login {
constructor () {
super()
this.name = '用户名登录'
this.elements = {}
}
initDom (options) {
const dom = document.createElement('div')
dom.innerText = this.name
const button = document.createElement('button')
this.elements.loginButton = button
dom.appendChild(button)
options.el.appendChild(dom)
}
initEvent () {
this.elements.loginButton.addEventListener('click', () => {
alert(this.name + '成功')
})
}
inited () {
alert('初始化成功')
}
}
// 使用方
const usernameLogin = new UsernameLogin()
usernameLogin.init({
el: document.querySelector('#usernameLogin')
})
思考
适用于对外 API 固定,API 内流程行为组成固定,但根据分类不同,需要各自实现一部分或全部流程内行为的类。
Factory Method 工厂方法模式
定义
当我们将生成实例的部分交给子类去实现时,就形成了工厂方法模式。
角色
- 抽象产品类:定义最终产品的 API。
- 实现产品类:实现抽象产品类的 API 并实现其他必要方法。
- 抽象工厂类:实现了生成实例的方法(流程),并负责定义组成此方法的抽象方法。
- 实现工厂类:负责实现抽象工厂类的抽象方法。
其中产品类的实例化在实现工厂类实现的某个抽象方法中。所以实现工厂类对实现产品类是已知的并配套的。
JavaScript 实现
// 抽象产品类
class Product {
init (options) {
throw new Error('需实现 Product 类的 init 方法')
}
}
// 实现产品类
class UsernameLogin extends Product {
constructor () {
super()
this.name = '用户名登录'
this.elements = {}
}
init (options) {
const dom = document.createElement('div')
dom.innerText = this.name
const button = document.createElement('button')
this.elements.loginButton = button
dom.appendChild(button)
options.el.appendChild(dom)
this.elements.loginButton.addEventListener('click', () => {
alert(this.name + '成功')
})
alert('初始化成功')
}
}
// 抽象工厂类
class Factory {
create () {
// 此处使用了模板方法模式,定义了生成实例的方式,但是此方法细节由子类实现
const product = this.createProduct()
this.register(product)
return product
}
createProduct () {
throw new Error('需实现 Factory 类的 createProduct 方法')
}
register () {
throw new Error('需实现 Factory 类的 register 方法')
}
}
// 实现工厂类
class UsernameLoginFactory extends Factory {
constructor () {
super()
this.id = 0
this.manager = {}
}
createProduct () {
return new UsernameLogin()
}
register (instance) {
this.manager[this.id] = instance
}
}
// 使用方
const factory = new UsernameLoginFactory()
const usernameLogin = factory.create()
usernameLogin.init({
el: document.querySelector('#usernameLogin')
})
思考
- 工厂方法的选择:
- 为避免过度设计,在仅仅只需要调用 new 就可完成实例生成的情况下,没有必要使用工厂方法模式。
- 在实例的生成是一套流程时,建议使用。如 JavaScript 实现示例,使用 new 实例化之后,还需完成注册,添加其他参数等固定流程,而这些流程复杂不适合包含在构造函数中(构造函数本身不建议太复杂)。这时将初始化的过程封装在工厂类中,将创建流程对用户屏蔽,那么初始化实现的修改对用户来说就是无感知的。
- 模板方法模式联系:如果工厂类创建 API 中用到的方法交给具体的子类实现的话,那么就用到了模板方法模式,如实例代码中的 create 接口中调用的 createProduct 和 register 就是由子类实现的。
- 简单工厂模式:
- 与工厂方法模式不同的是,简单工厂模式可能在 create 接口中直接封装了多种实例化方式,通过传入的参数条件判断,决定该走哪个流程。那么如果需要添加新的实例,则修改增加新的产品类,同时修改已经实现的 create 接口。对比工厂方法模式,则直接添加新的产品类和实现新的产品类,而不需要修改现有逻辑。
拓展
子类责任:在 JavaScript 中我们通常习惯将共有方法添加到父类(或原型链),这是站在子类(实例)的角度上思考的问题,希望上一层提供哪些方法。那么按照模板方法模式这种,则是站在父类的角度考虑问题,期望子类(或实例)实现哪些方法,也就是说子类具有实现在父类中所声明的抽象方法的责任,这种责任称为“子类责任”。