1 Structural Patterns
描述如何通过类的继承和对象间的组合来解决应用开发中常见问题。
1.1 The Adapter Patten
把一个已有类的某个接口变换成客户端所期待的另一个接口,从而使原本因接口不匹配而无法在一起工作的两个类能在一起工作。
有两种实现方式:
1.1.1 The Object Adapter
通过对象的组合实现。
1.1.2 The Class Adapter
通过类的继承来实现。
1.2 The Composite Patten
用于描述实际应用中经常遇到的对象间的树型层次关系。基本类图:
叶子(Leaf)和Branch(树枝)有共同的接口TreeNode,这样,客户端能以一种统一的方式访问树上每个节点。
Leaf下不能再有其他子节点,所以不支持get/add/remove儿子节点操作。而Branch应该有get/add/remove儿子节点的操作。
由于Leaf和Branch支持的操作不完全一致,具体实现可有两种方式:
1.2.1 Leaf与Branch通过不同接口区分
给定一个TreeNode node,当需要时,通过 “if (node instancof IBranch)”或“if (node instanceof ILeaf)”来判断其是Leaf还是Branch,从而获取其所支持的操作。
1.2.2 Leaf与Branch实现自共同接口
1.2.2.1 共同接口,不同实现
Leaf与Branch实现自共同的接口TreeNode,该接口定义树结点支持的操作的合集。Leaf对每一个不支持的操作给定一个空实现(即操作完成后,对Leaf节点无任何影响)。这样对于给定的TreeNode node,客户端无需区分是Leaf还是Branch,因为所有操作都可作用于其上。
1.2.2.2 共同接口,相同实现
在共同接口TreeNode中增加“boolean isLeaf()”方法,用于区分节点是Leaf还是Branch。
Java Swing中JTree的树节点就是采取这种设计方式。
1.3 The Decorator Pattern
对于需要通过基本功能的排列组合来产生大功能的应用场景,如果采用继承方式,势必
要为每种可能的组合提供一个子类。而Decorator模式却可以优雅的解决该问题。Java I/O类库的设计就大量应用了Decorator模式。
用户可以按需将多个Decorator的排列组合作用于ConcreteComponent之上,从而为基本function()增加新特性。
1.4 The Proxy Pattern
顾名思义,很好理解。通过Proxy,控制客户端直接创建和访问核心RealSubject。
实际应用中,会经常用到该模式,因此Java 2已提供java.lang.reflect.Proxy以及java.lang.reflect.InvocationHandler基础类,使得用户很容易为任一个类动态创建代理。
1.5 The Flyweight Pattern
实际上是单实例创建模式的扩展。ShareObjectFactory负责创建和向外提供有限个ShareObject,这些有限个ShareObject被整个系统共享。ShareObjectFactory自身一般设计为单实例。由于XXXShareObject的实例要被整个系统共享,所以一般被设计为轻量级(Flyweight)的不可变类,而对于依据运行情况可变的外部状态,一般作为类的method的参数传入。
1.6 The Façade Pattern
一个复杂的系统,应该对外提供统一,易于使用的Façade(门面)接口/类。客户端通过Façade与系统交互,而不是直接与该系统的内部组件/类通信。这样,易于设计分层的松耦合的各个子系统;且当一个子系统有变化时,至多对其Façade做调整,对系统其它部分没有影响。
举个例子,Hibernate作为一个ORM实现,本身是一个复杂的系统。但通过其对外提供的Configure/SessionFactory/Session/Query等接口或类,用户很方便使用,而这些接口或类就是Hibernate的Façade。
1.7 The Bridge Pattern
1.7.1 示例引入
个人认为,Bridge Pattern是结构模式中最难理解的一个。
好的示例胜于千言万语。假设要设计一个跨平台浏览器,而各种格式图片的显示是该浏
览器的一项重要功能。设当前要支持的图片格式有gif、bmp、jpg、png、pcx和tiff六种,而浏览器要支持的运行平台有Windows、Macinotosh和Unix三种。一种图片格式的内部数据结构表示(即图片二进制文件格式)显然是固定的,与其将在那个平台显示没有任何关系。而对给定的图片,如何加载和显示与平台相关,不同的平台有不同的加载和显示方式。
需求明了后,如何设计?一种直观的设计是定义一个接口Image,然后为每一种图片格式与平台的组合提供一个实现类,共6×3=18个。如下图所示:
如此设计带来的问题是:
1.子类过多;
2.子类可能有大量重复代码。例如,每个GifInXXX类的load()方法中都有对*.gif文件进行解析的代码片断,而这些代码都是相同的。
3.图片格式与图片显示平台在代码级别相互依赖。假设GIF图片格式内部数据结构发生变化,需要同时修改所有GifInXXX类;假设对Windows平台上图片显示效果有扩展,则需要同时修改所有XXXInWindows类。
因此,需要进一步分析,给出新的设计方案。面向对象分析的一种重要方法就是“找出可变点并对之封装(find what varies and encapsulate it)”。对这个系统,有两个可变点:
可变点1:图片加载/显示方式依据图片格式可变;
可变点2:图片加载/显示方式依据浏览器运行平台可变。
且这两个可变点应能够独立发生变化,无依赖。对可变点2抽象为一个接口PlatformPresentHandler,不同的平台给出不同的实现。不同格式的图片类都有一个对PlatformPresentHandler的引用handlder,用于动态指定图片的显示平台,同时封装仅与各自格式相关的加载/显示代码。完整的图片加载/显示功能由格式相关代码和handler相关method组合完成。最后将所有不同格式图片类进一步抽象为AbstractImage,以便浏览器能以一种统一方式处理不同格式图片。如下图所示:
这样设计,好处显而易见:
l 子类数目减少,只需6+3=9个;
l 当新增一种图片格式时,只需增加一个XXXImage类;现有某种图片格式发生变化时,只需修改对应的一个XXXImage类;
l 当新增一种支持平台时,只需增加一个XXXHandler类;对现有某种平台图片显示效果有新要求时,只需修改对应的一个XXXHandler类。
事实上,如此设计应用的就是所谓Bridge Pattern。从类图上来看,AbstractImage/PlatformPresentHandler就像是AXXXImage系列和XXXHandler系列之间的桥梁。
1.7.2 提炼
1.基本类图
2.适用场合
一个构件功能实现依赖多个可变点,而这些可变点又不相互依赖,可以独立变化。