为什么需要模板方法模式?
有时候我们需要做相似的一类事情,它们的总体步骤差不多,而细节上又有不同。我们希望抽象相同的部分,而一些子步骤又可以定制。甚至可以控制某些步骤是否要做。
比如一个薪酬管理的系统,假设有两种雇员:工人和工程师。
工人有基本工资,有计件工资。
工程师有基本工资,没有计件工资,有职级工资,职级越高工资越高。
所有员工都有加班费。
这时我们就可以在雇员超类中定义一个计算工资的模板方法,把相同的部分抽象,而具体的计算每种工资的子方法由子类完成。
什么是模板方法模式?
《head first设计模式》中是这样定义的:
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
具体来说,就是将算法的每个步骤定义成一个方法,再定义一个模板方法按一定次序调用这些方法。子方法可以是抽象的,由子类实现,也可以有默认实现,由子类选择是否要覆盖。
我们来实现一下上文中的工资计算方法:
首先实现Employee基类:
//salary.swift
class Employee {
var overtimeHours:Int = 0
let overtimePayUnit:Double = 40
func basePay() ->Double {
println("calculate base pay")
return 2000.0
}
func overtimePay()->Double{
println("calculate overtime pay")
return Double(overtimeHours) * overtimePayUnit
}
func levelPay()->Double{
return 0.0
}
func productPay()->Double{
return 0.0
}
func calcSalary()->Double{
println("calculate salary:")
return basePay() + overtimePay() + levelPay() + productPay()
}
}
然后实现Worker类,覆盖productPay()
方法计算计件工资。
class Worker: Employee {
var productCount:Int = 0
let payPerProduct:Double = 50.0
override func productPay() -> Double {
println("calculate product pay")
return Double(productCount) * payPerProduct
}
}
实现Engineer类,覆盖levelPay()
方法计算职级工资
class Engineer: Employee {
var level:Int = 0
let payPerLevel:Double = 2000.0
override func levelPay() -> Double {
println("calculate level pay")
return Double(level) * payPerLevel
}
}
运行一下看看:
//main.swift
var workerA = Worker()
workerA.overtimeHours = 15
workerA.productCount = 60
println("salary of workerA: \(workerA.calcSalary())")
var engineerB = Engineer()
engineerB.overtimeHours = 10
engineerB.level = 3
println("salary of engineerB: \(engineerB.calcSalary())")
结果如下:
calculate salary:
calculate base pay
calculate overtime pay
calculate product pay
salary of workerA: 5600.0
calculate salary:
calculate base pay
calculate overtime pay
calculate level pay
salary of engineerB: 8400.0
还是工程师工资高。好好读书是王道啊。
使用钩子控制模板方法的执行流程
有时候不仅仅需要在子类中定制模板方法中的具体步骤,还需要控制执行流程,这时可以使用钩子。
钩子是一个在父类中有默认实现的方法,子类可以选择是否要覆盖。
最简单的应用是实现一个返回布尔值的钩子,作为模板方法中if语句的判断条件,这样就可以通过覆盖钩子返回true或false来控制模板方法中代码的执行路径了。
使用钩子修改一下上文薪资计算的类:
class Employee {
var overtimeHours:Int = 0
let overtimePayUnit:Double = 40
func basePay() ->Double {
println("calculate base pay")
return 2000.0
}
func overtimePay()->Double{
println("calculate overtime pay")
return Double(overtimeHours) * overtimePayUnit
}
func needCalcLevelPay()->Bool{
return false
}
func levelPay()->Double{
println("zero level pay")
return 0.0
}
func needCalcProductPay()->Bool{
return false
}
func productPay()->Double{
println("zero product pay")
return 0.0
}
func calcSalary()->Double{
println("calculate salary:")
var salary = basePay() + overtimePay()
if needCalcLevelPay() {
salary += levelPay()
}
if needCalcProductPay() {
salary += productPay()
}
return salary
}
}
class Worker: Employee {
var productCount:Int = 0
let payPerProduct:Double = 50.0
override func needCalcProductPay() -> Bool {
return true
}
override func productPay() -> Double {
println("calculate product pay")
return Double(productCount) * payPerProduct
}
}
class Engineer: Employee {
var level:Int = 0
let payPerLevel:Double = 2000.0
override func needCalcLevelPay() -> Bool {
return true
}
override func levelPay() -> Double {
println("calculate level pay")
return Double(level) * payPerLevel
}
}
执行结果:
calculate salary:
calculate base pay
calculate overtime pay
calculate product pay
salary of workerA: 5600.0
calculate salary:
calculate base pay
calculate overtime pay
calculate level pay
salary of engineerB: 8400.0
和之前一样。
其实这个例子里并不需要钩子,不需要计算某一项薪资,直接返回0即可。但这是因为模板方法中调用的几个方法是完全独立的。如果有的方法有副作用,比如涉及IO、需要用户输入数据,就需要钩子来控制了。
另外这里只是简单地控制一个子步骤是否执行,有时候需要在多个子步骤中选择一个来执行,这时钩子就必不可少了。用代码来描述大概是这样:
class SuperClass {
func action1(){
println("action 1")
}
func action2(){
println("action 2")
}
func whichAction() -> Int {
return 0
}
func templateMethod() {
switch whichAction(){
case 1:
action1()
case 2:
action2()
default:
break
}
}
}
子类可以通过覆盖whichAction()
方法来控制执行action1()
还是action2()
。
(未完待续)