桥接模式 VS 装饰器模式、状态模式 VS 策略模式的微妙之处
Foundations of Software Engineering 的 Design Pattern 学习笔记整理
关键词:设计模式、桥接模式、装饰器模式、状态模式、策略模式( Design Pattern, Bridge Pattern, Decorator Pattern, State Pattern, Strategy Pattern)
本文简述了桥接模式(Bridge Pattern)和装饰器模式(Decorator Pattern),以及状态模式(State Pattern)和策略模式(Strategy Pattern)的微妙之处,以及它们之间是如何“看起来就像另一者的”。
桥接是指能够沿着不同的维度(along distinct dimensions)以多种方式(in more than one way)来指定一个对象,通常使用子类型(sub-typing)和注入(injection)
new BoldText("blah blah", new UTF16Encoding))
new ItalicsText("blah blah", new ASCIIEncoing))
// where BoldText and ItalicsText are subclasses of Text and UTF16Encoding and ASCIIEncoding are subclasses of TextEncoding
装饰器是指能够以一种任意的方式向一个对象添加特征(responsibilities, embellishments, or features),通常使用包装(wrapping)
装饰器所描述的是一种 specialization,但是有着截然不同的机制,并且可以创建的变化不一定是不同维度的(variations you can create are not points on distinct dimensions):在同一维度上,多个特征可以同时添加到一个对象上。使用装饰器模式需要依赖继承,但是子类型却并不特殊化目标对象(subtyping does not specialize the target object),我们可以使用包装来完成这个事情(injection it into a higher level object (wrapper) does the trick)。
new Underlined(new Italics(new Bold(new Text("blah blah")))).
在上面这个例子中,我们可以使用装饰器模式将 blah blah
变成斜体的、加粗的、带下划线的表示,但是不能使用桥接模式将这段文本变成既是加粗又是斜体的,因为 Bold 和 Italics 都是同一个维度下的分化(specializations along the style dimension),你必须选择其中之一。同样,UTF16 和 ASCII 是编码维度下的不同分化,你也必须选择其中之一。
如果你想用桥接模式创建一个既是粗体、又是斜体的风格,那么你就必须定义一个名为 BoldAndItalicsText
的 Text 的子类,这显得不合理是吗?是的,所以,你就会意识到,粗体、斜体当然是可以被一起使用的,因为它们是特征,而不是单一维度下面的不同分化,所以你需要的其实是装饰器模式,而不是桥接模式。
再来回顾一下这个使用桥接模式的经典例子:在一个维度上,我们有 NoSQL DB、SQL DB 和 Mock DB,它们是单一维度下的不同分化,而在另一个维度上,我们有 Backlog DB 和 Product DB 等。
状态模式和策略模式意外地有着类似的类图,但是它们的意图是不同的。
状态不仅仅表达了在运行时去改变一个对象的行为,它更加强调的是一个对象能够识别它自己的内部状态并相应地改变它的行为(也可能是改变它的状态,因此需要在对象内部实现一个状态机)。
作为策略模式的典型例子,我们会在一个机器人对象中注入一个不同的防撞策略,这时我们并没有更改机器人的内部状态,所以这仅仅是选了不同的策略。当我们在一个项目经理对象中注入一个不同的报告生成过滤器时,我们没有改变内部状态,只是改变了在任何状态下的报告的打印策略。
但是,如果需要一台自动售货机在有足够的钱存入时与钱不够时表现不同,这就是关于状态的了。自动售货机在有足够资金的时候会切换状态,并且在每个状态下只能执行某些行为。
因此,意图在设计模式中是很重要的。不同的意图意味着,不同的选择。