用三种语言实现模板方法模式
领会其心,不拘一法。
模板方法模式,顾名思义,就是一个方法里有一些固定的模板部分,有一些可变的部分。模板方法模式的实质是,将流程中的固定和可变部分分离。
本文用三种语言来实现模板方法模式。
实现模板方法模式
Java
Java 实现模板方法模式的标准套路是:
- 定义一个接口;
- 定义一个抽象类,实现接口的方法,但是实现方法流程里留有一个未实现的部分,方法签名为 abstract;
- 定义一个子类,实现抽象类中的 abstract 方法。
代码如下:
package zzz.study.pattern.tempatemethod;
import org.junit.Test;
/**
* TemplateMethodTest:
* created by qin.shu 2023/11/4
*/
public class TemplateMethodTest {
@Test
public void testCook() {
Cook cookTomato = new CookTomato();
cookTomato.doCook();
Cook cookMeat = new CookMeat();
cookMeat.doCook();
}
}
interface Cook {
void doCook();
}
abstract class AbstractCook implements Cook {
public void doCook() {
wash();
pour_oil();
cook();
chuguozhuangpan();
}
abstract void cook();
private void wash() {
System.out.println("wash");
}
private void pour_oil() {
System.out.println("pour_oil");
}
private void chuguozhuangpan() {
System.out.println("chuguozhuangpan");
}
}
class CookTomato extends AbstractCook {
@Override
void cook() {
System.out.println("cook tomato");
}
}
class CookMeat extends AbstractCook {
@Override
void cook() {
System.out.println("cook meat");
}
}
Go
接着,咱们用 Go 来模拟上面这段程序。
不过 Go 是没有类继承的语言特性的。Go 的接口“实现”即是基于行为:实现了接口的所有方法,就是实现了接口,无需特意用 implements 关键字表明。
Go 代码如下(这个例子来自网上示例):
package main
import (
"fmt"
)
type Cooker interface {
wash()
pour_oil()
cook()
chuguozhuangpan()
}
// 类似于一个抽象类
type CookFlow struct {
}
func (CookFlow) wash() {
fmt.Println("wash")
}
func (CookFlow) pour_oil() {
fmt.Println("pour_oil")
}
// 做菜,交给具体的子类实现
func (CookFlow) cooke() {
}
func (CookFlow) chuguozhuangpan() {
fmt.Println("chuguozhuangpan")
}
// 封装具体步骤
func doCook(cook Cooker) {
cook.wash()
cook.pour_oil()
cook.cook()
cook.chuguozhuangpan()
}
type Tomato struct {
CookFlow
}
func (t *Tomato) cook() {
fmt.Println("cook tomato")
}
type Meat struct {
CookFlow
}
func (m *Meat) cook() {
fmt.Println("cook meat")
}
func main() {
tomato := &Tomato{}
doCook(tomato)
meat := &Meat{}
doCook(meat)
}
可以发现,Go 没有类继承,所以模板方法实现是放在函数里的。通过在函数参数中传入子类来实现。
对于 Go 这样的非对象语言来说,用对象语言思考,总会有点“拿着锤子把所有东西都看成钉子”的感觉。咱们现在来褪下“对象的外衣”,直视“模板方法模式的本体”,用函数式编程来实现。
可以看到,算法无非是一个编排,固定一部分,让另一部分可变。
实际上,我只消写一个模板方法实现,把可变部分作为函数参数传入即可。
package main
import "fmt"
func cook(doCook func()) {
wash()
pour_oil()
doCook()
chuguozhuangpan()
}
func wash() {
fmt.Println("wash")
}
func pour_oil() {
fmt.Println("pour_oil")
}
func chuguozhuangpan() {
fmt.Println("chuguozhuangpan")
}
func main() {
cook(func() {
fmt.Println("cook tomato")
})
cook(func() {
fmt.Println("cook meat")
})
}
Python
流程,本质上就是函数的编排。咱们可以写得更通用些。
python 对函数式编程支持更好,编写起来更好。
如下代码所示: 将一个函数列表传入,遍历并调用,就实现了一个流程编排能力。然后把具体函数传入即可。是不是超简单?
def do_cook(cook_funcs):
for func in cook_funcs:
func()
def wash():
print('wash')
def pour_oil():
print('pour oil')
def cook_tomato():
print('cook tomato')
def cook_meat():
print('cook meat')
def pot():
print('pot')
if __name__ == '__main__':
do_cook([wash, pour_oil, cook_tomato, pot])
do_cook([wash, pour_oil, cook_meat, pot])
还可以用函数 curry 来实现。所谓 curry, 就像高中所学的多元函数,传入一个参数,就可以得到另一个函数。比如 f(x,y) = x + y ,将 x = 1 传入,就得到了函数 f(y) = 1 + y。咱们用 curry 来实现模板方法模式。
def do_cook_by_curry(wash, pour, pot):
def after(cook):
wash()
pour()
cook()
pot()
return after
if __name__ == '__main__':
print('template method pattern using func curry')
cook_template = do_cook_by_curry(wash, pour_oil, pot)
cook_template(cook_tomato)
cook_template(cook_meat)
注意到:这里 do_cook_by_curry 函数的返回结果是一个函数,这个函数会传入一个可变的函数作为参数来调用。这样就实现了模板方法模式。是不是感觉很“犀利” ?
函数式编程与对象编程
函数式编程与对象编程都属于一种编程范式。那么,这两者各有什么优劣呢 ?
- 函数式编程:基于不可变原则,短小精悍,通过函数组合能够快速建立强大的能力;
- 对象编程:将状态和行为统一在对象里,其思想贴合事物的整体性。
对象编程,适合管理具有大量状态的工程(比如基于 DDD),适合于包装,本身是一种工程手段;函数式编程,适合实现强大的功能,更加纯粹。
对于很多 Java 工程来说,其实很多组件都是单例。数据都在数据对象里。如果领域比较复杂,对象和行为比较复杂,需要用 DDD 的思想来包装状态和行为,则用对象编程比较适合,否则不如直接用函数式编程,反而更加简洁。
函数式编程 + 泛型编程结合,是一种强大的编程技艺。若能娴熟掌握,可以甩同龄人一条街。
小结
本文用三种编程语言来实现模板方法模式。其主旨在于,用不同的思想和视角去看待同一件事情。这种方式可以开阔技术视角,不局限于某一种编程语言和平台。
当我们领会模式的思想和本质后,其实现手段和具体招式就可以随心变化。
领会其心,不拘一法。