Middleman Patterns: Adapter / Proxy / Decorator
我觉得用 Java 学 design pattern 有个问题就是:总是要有 interface / abstract class 介入,搞得整个 class hierarchy 非常复杂。其实我用 duck typing 的思路来看,Adapter、Proxy、Decorator 这三个模式的基本结构是一样的,都是 客户端、中间商、旧实现。你撇开 interface layer 看下面三个图,我只能说是完全一致!
+------+ +---------+ +------------+
| User |------->| Adapter |------->| RealObject |
+------+ +----+----+ +-----+------+
| |
v v
+--------------+ +---------------+
| NewInterface | | RealInterface |
+--------------+ +---------------+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+------+ +-------+ +------------+
| User |-------->| Proxy |-------->| RealObject |
+------+ +---+---+ +-----+------+
| |
| v
| +---------------+
+----------->| RealInterface |
+---------------+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+------+ +-----------+ +------------+
| User |------->| Decorator |----->| RealObject |
+------+ +-----+-----+ +-----+------+
| |
| v
| +---------------+
+---------->| RealInterface |
+---------------+
撇开 class hierarchy,这里先看看这三个 pattern 在功能上的区别:
- Adapter: 接口修正
- Proxy: 接口 Access Control
- Proxy 并不会给 User 提供新的接口,也不会修改旧的业务逻辑 (behavior),它提供的都是 side effect
- 比如 security / caching / lazy loading
- Decorator: 维持原接口,但可能会修改业务逻辑 (behavior) 或者添加新的接口
- “添加新的接口” 是可能的,比如我们可以把 Decorator 设计成 Interface which
extends RealInterface
或者 abstract class
- “添加新的接口” 是可能的,比如我们可以把 Decorator 设计成 Interface which
假设我们用 $\Lambda$ 表示 RealObject
提供的接口,用 $\Lambda’$ 表示 中间商 提供的接口,于是有:
那至于 class hierarchy,我觉得这是 Java 的设计技巧,这里就不谈了。这三个 patterns 都有一个明显的特点是:中间商 holds an instance of RealObject
,再次体现 “组合优先于继承”。
扩展:
- 可以有 Proxy Chain (体现 Single Responsibility Principle,一个 Proxy 只做一方面的功能)
- 同理可以有 Decorator Chain
20211021 补充:
我们这一篇的侧重点是接口和行为,但 Decorator 也可以作为一种 “组合” 的方式 (用来生产子类)。比如 Head First Design Patterns 的例子:如何给 coffee shop 设计一个 “Dark Roast with Mocha and Whip” 类?
我们可以简单地用组合,比如:
class DarkRoastWithMochaAndWhip:
def __init__(self):
self.ingredients = [DarkRoast(), Mocha(), Whip()]
也可以用 Decorator Chain:
class DarkRoastWithMochaAndWhip:
def __init__(self):
self.ingredients = Whip(Mocha(DarkRoast(None)))
假设现在我要实现 def get_price(self): ...
,那么上面两种写法的实现方式是不同的。简单说就是 “平铺式组合” 和 “嵌套式组合” 的区别。另外在实际应用中,是否需要定义 DarkRoastWithMochaAndWhip
这么一个子类?还是直接用组合来代替?这是另外需要考虑的问题,并不是说本文的写法就是最合适的。
Comments