从装饰器到函数组合

这是「从函数式角度看设计模式」的第五篇。

装饰器,按照定义,是一种「通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为」的设计模式。一般来说,是在「基类无法被继承或者不想继承基类的情况下复用并扩展基类」的方法。大体上也是使用了组合的概念。

一个典型的OOP写法大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
interface IBase {

void func1();
}

class Base : IBase { // 要扩展的基类,必须暴露一个公共接口 IBase

void func1();
...
}

class BaseDecorator : IBase { // 继承自 IBase 的装饰器基类

protected IBase _base;

BaseDecorator(IBase _base) { ... }

override void func1() => _base.func1();

abstract void func2(...);
}

class ConcreteDecoratorA : BaseDecorator {

ConcreteDecoratorA(IBase _base) : base(_base) { ... }

override void func2(...) => ...
}

class ConcreteDecoratorB : BaseDecorator {

ConcreteDecoratorA(IBase _base) : base(_base) { ... }

override void func2(...) => ...
}

var baseComponent = new Base();
var componentA = new ConcreteDecoratorA(baseComponent);
var componentB = new ConcreteDecoratorA(baseComponent);

executeWithComponentA(componentA);
executeWithComponentB(componentB);

不难看到装饰器的优点大概是,不需要修改也不需要继承自Base基类,所以不用担心破坏原有的代码或者添加耦合。稍微跑个题的话,这里也可以看出使用interface而不是继承自抽象类的优点,因为如果没有暴露出IBase接口的话,装饰器是写不出来的。

装饰器对应的函数式编程概念是组合,也就是把多个函数串接起来形成新的函数的能力,例如,把A → BB → C组合起来就可以得到A → C,对一个值x,先后使用函数g和f的结果,也就是f(g(x)),等价于把两个函数组合后使用的结果,也就是(f . g)(x)

同样的概念在函数式的语言里就可以这样表达了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
baseComp :: A
baseComp = ...

decorator :: (A -> B) -> A -> B
decorator deco base = do
base
deco base

decoA :: A -> B
decoA base = ...

decoB :: A -> B
decoB base = ...

compA = decorator decoA baseComp
compB = decorator decoB baseComp

main = do
...
funcA compA
...
funcB compB
...

总的来说,这套写法还是比较累赘,不过不难看出和OOP写法的对应之处。这也体现了函数本身可以被扩展的特性。这种特性其实也是函数式编程里经常强调的东西了。


从装饰器到函数组合
http://inori.moe/2023/10/27/from-decorator-to-composition/
作者
inori
发布于
2023年10月27日
许可协议