软件设计要素初探:基础设计模式概览
在 “软件设计要素初探” 一文,尝试从软件设计的整体角度,综合讨论了软件设计的各种要素。本文讨论基础设计模式。
概述
设计模式是实现特定需求的接近代码层面的设计套路,通常用于梳理和表达对象之间较为复杂的依赖和交互关系,将错综复杂的容易膨胀的难以理解和扩展的条件判断逻辑解开成一系列对象的清晰可理解的易扩展的交互结构。不同的设计模式之间有一些形式上的相似点,区分在于关注点不同。
设计模式是软件设计的入门指南,其主要理念是: 面向接口的编程,组合与委托优先于继承。理解设计模式的关键点:关注点及目标用途、协作对象、静态结构及动态交互。
基础模式
常用的基础设计模式有:
工厂模式
- 工厂模式(FactoryMethod):根据不同条件为客户端创建实现相同接口和功能的实例,而客户端使用的是统一的功能接口。有点类似策略模式,不过工厂模式创建的是具备状态和行为的实例,而策略模式则是对行为和算法建模。典型示例有 Spring Bean 工厂。 很多模式也需要联合工厂模式,比如策略模式、模板方法模式等。
单例模式
- 单例模式(Singleton):为某类服务创建唯一的全局访问点。多线程环境下需考虑并发创建问题,可使用枚举。典型示例有 Spring Component/Service 。
生成器模式
- 生成器模式(Builder):复杂对象的创建,需要多个步骤或组合多个子部件完成,并且在这些步骤或组件完成之前,不允许该实例的生成。通常会私有化构造器,在构造的每一步骤中会设置某些属性并返回其自身,便于链式调用,提供一个build方法生成满足某种实例创建契约的最终实例。比如汽车制造,就适用生成器模式。
可参阅 lombok 库的 @Builder 注解。
抽象工厂模式
- 抽象工厂模式(AbstractFactory): 系统含有多个相互协作的不同功能和特性的组件,每个组件可定制为不同的种类和风格。抽象工厂为每个组件的创建提供相对应的抽象工厂的创建接口,而由具体的工厂实现对应的组件创建接口。即有:XXXComponent 对应 XXXAbstractFactory , XXXAbstractFactory 含有一个 createXXXComponent 的创建接口,这里 XXXComponent, XXXAbstractFactory 都是接口而不是实现。抽象工厂用于创建协作的多个组件实例的接口骨架。
原型模式
- 原型模式(Prototype): 从现有对象实例快速生成和定制化对象实例,而不需要创建大量子类。原型模式特别适合于定制化。比如,现有一个房间对象,要创建含有炸弹的房间对象;这些房间对象的房间属性都是一样的,而这些炸弹可能是不同种类,也可能是相同种类只是颜色不同,使用普通的继承结构和初始化操作会产生大量的相同的赋值代码。此时使用原型模式非常恰当。通过一个含有普通炸弹的房间对象作为原型进行克隆,然后设置炸弹的种类和属性即可。原型模式使用有两点:需要一个原型管理器进行原型注册和查找;根据场景正确地实现浅拷贝和深拷贝。为原型静态或动态地增加属性和行为,可以轻易地为所有从该原型衍生的对象添加属性和行为。Javascript语言提供了原型机制。
适配器模式
- 适配器模式(Adapter):复用现有类或对象的功能,实现客户端需要的接口。有类适配器和对象适配器两种。类适配器继承现有类,可以重新定义部分行为;对象适配器使用现有类的对象实例并委托实现给该对象,可灵活使用多个现有对象。通常使用对象适配器。
组合模式
- 组合模式(Composite): 具有层次性结构的单个对象与组合对象具有一致的接口,客户端能够使用统一的接口操作局部和整体。树形组件是典型示例。
外观模式
- 外观模式(Facade):为复杂子系统提供简单易用的接口,屏蔽实现的复杂性。库的API,Java的正则对象(Compiler,Matcher)是典型的外观模式应用。
装饰器模式
- 装饰器模式(Decorator):具有相同接口的对象可以在运行时动态组合成具有复合行为的对象。可以在运行时为现有对象动态添加任意复杂的额外行为。典型示例有 JavaIO框架。函数式编程是实现装饰器模式的强大机制。
可参阅:“Java函数接口实现函数组合及装饰器模式” ,“使用IntelljIDEA生成接口的类继承图及装饰器模式”
代理模式
- 代理模式(Proxy): 为对象或服务提供间接访问机制。提供间接访问的原因可能有:隐藏源对象或源服务的部署细节(远程代理);源对象创建开销很大,只有需要的时候才创建(虚代理);访问源对象需要某种权限或同步互斥(保护代理);需要为访问源对象和源服务增加额外的行为(智能代理)。代理对象与源对象通常有一致的接口,并持有源对象的引用。
桥接模式
- 桥接模式(Bridge):抽象部分与实现部分各具有独立的继承层次结构分开变化发展,使用桥接器将抽象继承层次与实现继承层次连接起来,实现并不直接继承抽象类,而是抽象类持有实现类的引用。通常适用于这样的场景:创建一个标准协议的接口框架,而接口框架的实现可以有多种选择,客户端可以根据需要选择不同的实现。桥接器通常与抽象工厂模式、单例模式组合使用。典型应用有JDBC的接口与实现、日志API接口与实现。
享元模式
- 享元模式(Flyweight):大量不可变小对象的共享与管理。比如基础类型里的常用的26个字符对象、256个小整数对象、化学元素及原子等。可使用缓存技术实现。为了尽可能节省内存开销,需要识别享元对象中的外部状态并从中移除,使得享元对象的共享达到最大化。享元模式可与抽象工厂模式、组合模式联合使用。应用示例有使用面向对象技术创建的文档编辑器。
模板方法模式
- 模板方法模式(TemplateMethod):预先定义处理流程,对于流程中某些环节的具体处理留给具体业务去完成。模板方法模式应指明哪些是钩子操作(可选重定义的,有默认实现),哪些是抽象操作(必须重定义的,没有默认实现)。对于抽象操作,可以使用统一命名前缀来识别,比如mustDoXXX. 典型示例有 Java容器框架。
可参阅:“设计模式之模板方法模式:实现可扩展性设计(Java示例)”
策略模式
- 策略模式(Strategy):为特定任务提供多种实现算法,并在不同场景下可灵活切换而对客户端透明。比如批量拉取数据的顺序策略(简单而略慢)以及并发策略(快速而略复杂)。使用策略模式时,客户端可持有策略实现对象的接口或者策略实现对象的模板接口参数。具体应用例子有排序,提供多种排序策略,客户端可根据需要采用所需要的排序实现(简单性、稳定性、性能、内存占用、多关键字、内存排序、文件排序)。
可参阅:“设计模式之策略模式:分离不同实现(Java示例)”
观察者模式
- 观察者模式(Observer):每个对象都有一系列观察者,当对象发生更新时推送更新给观察者,由观察者决定具体行为。通常引申为订阅-消费模式。GUI应用是观察者模式的典型应用之一。
职责链模式
- 职责链模式(ResponsibiltyChain):将能够处理某种请求的多个对象串联成一条处理链,请求沿着处理链传递给这些对象,对请求的发送者隐藏处理请求的实现细节(请求的发送者不需要知道谁真正处理这个请求)。 责任链上的请求处理对象可以在运行时动态增减。责任链的实现中,链的构造和请求的传递既可以使用全局控制器来配置、生成和控制(集中式策略),也可以通过链上的对象来负责引用和传递(分散式策略)。需要构造两种接口:请求接口和请求处理对象的接口,而请求处理对象则需要实现请求接口。请求处理对象的顺序也值得斟酌,通常是从特殊到普遍的顺序。异常捕获和处理机制即是职责链模式的具体应用例子之一。
命令模式
- 命令模式(Command):将行为封装到通用接口中,通过命令调用。类似回调接口,可以使用函数式编程实现。具体应用例子有JDBCTemplate。
可参阅: “统计方法运行时间【Java实现】”
迭代器模式
- 迭代器模式(Iterator):为复杂的组合对象结构返回迭代器,安全访问其中的元素,隐藏复杂的结构细节。典型示例有Java容器的迭代器,JDBC的ResultSet。
状态模式
- 状态模式(State): 一个对象具有多种状态,其某种复杂行为取决于状态及状态变迁(代码实现通常是一个大的Switch-If语句)。此时,可以单独将状态及行为抽离出来建模,并将对象的行为委托状态对象完成。若状态行为比较简单,可以使用具有行为的枚举或表来实现;若状态行为比较复杂,则需要使用状态抽象类及具体状态子类来实现。状态对象可持有对象的引用,便于访问所需要的信息。状态子类没有实例属性,可以实现为单例类。常见的应用有:TCP连接状态管理、虚拟机状态管理、订单状态管理。
中介者模式
- 中介者模式(Mediator):有多个基础服务方,多个业务方;每个业务方都有自己需要定制化的东西。如果所有业务方直接与各个基础服务方打交道,会导致“服务方-业务方”的依赖关系激增(乘法关系),交互也变得复杂。引入中介者对象作为服务方和业务方的调度调控,每个基础服务方和业务方只需要与中介者交互,而不需要知道具体的服务方和业务方。中介者对象将交互行为集中化,简化了依赖关系数量(加法关系)。 中介者对象在应用组织架构中俗称“中台”。
解释器模式
- 解释器模式(Interpret):如果特定问题可以使用一种简单的语言来描述,那么可以定义一种文法,以及对应的解释器。将问题的求解归结为根据文法构造句子,然后使用解析器来解释句子生成结果。适用于领域语言设计和实现。典型例子有用于字符串匹配的正则表达式解析以及JSON解析。
可参阅: “Java实现图案打印: 记法与解释器”
访问模式
- 访问模式(Visitor):一个复杂的复合对象含有多个子对象 A,B,C, 对于每个子对象都需要定义操作 op1, op2, op3, ... , opN ,而每个子对象的操作 opi 都可能是不一样的。 全部操作的定义放在这个复合对象里,将使得这个复合对象异常的庞大臃肿。使用访问器模式,可以将这些操作抽离出来。每种操作都是一个访问器ConcreteVisitor,可以定义访问接口visitA,visitB,visitC ; 每个子对象以及该复合对象都有一个 accept(Visit) 的方法。复合对象的 accept 实现将委托给遍历子对象的 accept 。 Visitor 主要用于为复合对象定义可扩展的新操作。当定义一个新操作时,只需要增加一个新的ConcreteVisitor,而不需要改动现有的其他类。如果子对象变化频繁,那么每个 Visitor 都要增加一个 visitorX 的方法,Visitor模式就不适用了。
备忘录模式
- 备忘录模式(Memento):历史状态存储、撤销与回退。
模式组合
组合是力量之源。每个设计模式都有某个关注点,而这些关注点是相互协作的;具有协作关系的模式也是可以组合的。可以阅读《面向模式的软件架构》系列学习基础模式如何组合而得到更大的实用架构模式。这里引用《设计模式:可复用面向对象软件的基础》的图:
模式示例
可参考个人工程 ALLIN 下的 src/main/java/zzz/study/patterns 包下的文件。