高阶函数和模版方法模式

这是「从函数式角度看设计模式」的第三篇。目的是从和OOP不同的角度重新审视设计模式。

实际上感觉都没什么好写的?

这是「模版方法模式」的定义和实现方法:

模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。

模板方法模式建议将算法分解为一系列步骤, 然后将这些步骤改写为方法, 最后在 “模板方法” 中依次调用这些方法。 步骤可以是抽象的, 也可以有一些默认的实现。 为了能够使用算法, 客户端需要自行提供子类并实现所有的抽象步骤。 如有必要还需重写一些步骤 (但这一步中不包括模板方法自身)。

一个典型的例子是RPG游戏里,有不同的角色和职业,但是都会做出攻击防御之类的同样类型的动作,或者是一个数据处理软件,需要对不同格式,例如TXT、MD、HTML进行类似的文字处理动作。

但是仔细想,这不就是高阶函数嘛。比如说文字处理的这个例子,基类就可以设计为留下一个「坑」,接受一个lambda,来描述具体的处理流程,而不需要关心具体哪个格式处理哪种文件。

1
2
3
4
5
fun textProcessor(formatRelated: () -> Data) {
...
val data = formatRelated()
...
}

这里的lambda是一个Supplier(从Java的角度),但是实际上根据需求也可以有任意的入参,并且有或没有返回值。

拿到了这个lambda后,在主函数内就可以对他进行各种变换了。

1
2
3
4
5
6
7
fun txtProcess(): Data = ...
fun markdownProcess(): Data = ...
fun htmlProcess(): Data = ...

textProcessor(txtProcess).let { ... } // 这里的 data 就是 txt 文档
textProcessor(markdownProcess).let { ... } // 这里的 data 就是 markdown 文档
textProcessor(htmlProcess).let { ... } // 这里的 data 就是 html 文档

这也是典型的「依赖注入」,虽然没有用到一行注解,但是具体的算法流程被外置了。

相比之下,模版方法模式大概是这样的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Base {
abstract fun dependentFun1()
abstract fun dependentFun2()

abstract fun concreteFun() {
...
val a = dependentFun1()
...
val b = dependentFun2()
...
}
}

class TypeA : Base {
override fun dependentFun1() ...
override fun dependentFun2() ...
}

比起OOP设计模式,有什么好处呢?

最大的好处大概是不再需要声明类型的继承关系。这样一来,一方面少写了很多代码,另外一方面lambda注入的关系是松耦合的,而继承是强耦合的。Lambda的形式显然可维护性也会好许多。

实际上,模版方法模式倒不如说是在用OOP的继承关系的「多态」来模拟这种「算法不依赖于基类」的关系。不过呢,函数式的做法,本就可以更好地表达「多态」关系,至于控制反转,更是回调天然可以表达出来的。


高阶函数和模版方法模式
http://inori.moe/2022/12/04/hof-and-templates-patterns/
作者
inori
发布于
2022年12月4日
许可协议