- 本博客为哈工大计算机科学与技术学院大二软件构造课程的课件翻译。同时记录了部分本人上课时的学习笔记和感悟
- 该博客10500字左右,主题为4-3面向复用的设计模式,已经全部更新完成
- 由于水平有限,翻译可能不是特别流畅、通顺,并且存在一定错误,观点、笔记不一定完全正确,敬请各位批评指正!
目录
-
结构型模式:
- 适配器模式(Adapter)通过将自己的接口封装在已经存在的类的接口周围,允许具有不兼容接口的类一起工作。
- 装饰器模式(Decorator)在对象的现有方法中动态地添加/重写行为。
- 门面模式(Facade)为大量代码提供了一个简化的接口。
-
行为型模式
- 策略模式(Strategy)允许在运行时动态地选择一系列算法中的一个。
- 模板模式(Template method) 将算法的框架定义为抽象类,允许其子类提供具体的行为。
- 迭代器模式(Iterator)按顺序访问对象的元素,而不暴露其底层表示域(rep)。
- 除了Framework,4-2节所讨论的其他复用技术都过于“基础”和“细小”,有没有办法做更大规模的复用设计?
- 本节:几种典型的“面向复用”的设计模式
阅读材料
- CMU 17-214:Sep 12、Sep 17
- 设计模式:第1、2章;第4.1、4.4、4.5、5.4、5.9、5.10节
- 阅读是学习理解的关键
为什么要使用面向可复用性的设计模式
一个设计……
……使得便于更改的灵活性成为可能(可重用性)
……在修改旧问题时最小化新问题的引入(可维护性)
……允许在初始交付之后交付更多的功能(可拓展性)
设计模式:针对软件设计中给定情景下,经常出现问题的通用的、可重用的解决方案。
面向对象的设计模式:通常呈现Class和Object之间的关系和交互,而不指定所涉及的最终应用程序的Class或Object。
四人帮:设计模式:可重用的面向对象软件的元素( Design Patterns: Elements of Reusable Object-Oriented Software )
作者:
By GoF (Gang of Four) – Erich Gamma – Richard Helm – Ralph Johnson – John Vlissides
设计模式的分类方法
- Creational patterns 创建型模式
- 关注对象创建的过程
- Structural patterns 结构型模式
- 处理类或对象的组合
- Behavioral patterns 行为类模式
- 描述类或对象交互和分配职责的方式
1、结构型模式
(1)适配器模式Adapter
- 意图:将一个类的接口转换成客户端期望得到的另一个接口
- 适配器让那些由于接口不兼容而无法一起工作的类一起工作
- 用一个新接口包装一个现有的类。
- 对象:将旧组件重用到新系统(也称为“包装器”)
例子:
一个 LegacyRectangle组件的display()方法期望接受“x,y,w,h”参数
但是 客户想要通过左上的x和y和右下的x和y
这种不一致可以通过添加额外的间接层(即适配器对象)来协调。- - - - -委托
例子:不用适配器模式
例子:用适配器模式
例子
(2)装饰器模式Decorator
装饰器模式的启发性举例:
-
假设您需要Stack数据结构的各种扩展……
- UndoStack:允许您撤消以前的push或pop操作的堆栈
- SecureStack: 需要密码的Stack
- SynchronizedStack:序列化并发访问的Stack
- 用每个子类实现不同的特性
-
如果是任意组合的拓展呢?
- SecureUndoStack:一个需要密码的栈,并且允许你撤销之前的操作
- SynchronizedUndoStack:一个序列化并发访问的栈,并且允许你撤销之前的操作
- SecureSynchronizedStack: …
- SecureSynchronizedUndoStack: …
继承层次结构?多继承?
继承的限制:
-
结合继承层次结构
- 组合爆炸
- 大量的代码重复
-
难题:您需要对单个对象进行任意或动态可组合的扩展。
-
解决方案:将公共接口实现为要扩展的对象,添加功能,但将主要职责委托给底层对象。
-
影响
-
Decorators同时使用子类型和委托
装饰器模式的例子
ArrayStack类
AbstractStackDecorator 类
具体装饰器类
使用装饰器类
装饰器模式VS继承
- Decorator在运行时合成特性
- 继承在编译时合成特性
- 装饰器由多个协作对象组成
- 继承产生一个单一的、明确类型的对象
- 可以混合和匹配多重装饰
- 多重继承在概念上是困难的
java.util.Collections的装饰器模式
- 将一个可变列表变为一个不可变列表:
- 类似的对于synchronization: :
(3)门面模式Facade
- 难题:客户群体的一部分需要一个简化的接口来实现复杂子系统的整体功能。
- 意图: 提供一个统一的接口来取代一系列小接口调用,相当于对复杂 系统做了一个封装,简化客户端使用
- 为子系统中的一组接口提供统一的接口。Facade定义了一个更高级的接口,使子系统更易于使用。
- 用更简单的接口包装一个复杂的子系统。
- 这减少了成功利用子系统所必需的学习曲线。
- 它还促进了子系统与其潜在的许多客户端之间的解耦。
Facade模式应用于类似类型的接口,其目的是提供单个接口,而不是多个执行类似工作的接口。
门面模式举例
- 假设我们有一个应用程序,它有一组使用MySql/Oracle数据库的接口,可以生成不同类型的报告,如HTML报告、PDF报告等。
- 因此,我们将有不同的接口集来处理不同类型的数据库。
- 现在,客户机应用程序可以使用这些接口来获得所需的数据库连接并生成报告。
- 但是,当复杂性增加或接口行为名称令人困惑时,客户端应用程序将发现很难管理它。
- 因此,我们可以在这里应用Facade模式,并在现有接口之上提供包装器接口来帮助客户端应用程序。
MySQL和Oracle两个辅助类
门面类
客户端代码
2、Behavioral patterns行为型模式
(1)Strategy策略模式
- 难题:针对特定任务存在不同的算法,但是客户端可以在运行时根据动态上下文在算法之间进行切换。
- 有多种不同的算法来实现同一个任务,但需要client根据需要 动态切换算法,而不是写死在代码里
- 示例:对客户列表进行排序(冒泡排序、归并排序、快速排序)
- 解决方案:为算法创建一个接口,为算法的每个变体创建一个实现类。
- 为不同的实现算 法构造抽象接口,利用delegation,运行时动态传入client倾向的算法 类实例
- 优点:
- 易于扩展的新算法实现
- 分离算法从客户端上下文
代码示例
(2)Template Method 模板模式
- 问题:几个客户端共享相同的算法,但在细节上有所不同。,算法由可自定义部分和不变部分组成。公共步骤不应该在子类中重复,而是需要重用。
- 做事情的步骤一样,但具体方法不同
- 例子:
- 执行测试用例的测试套件
- 打开、读取、写入不同类型的文档
- 解决方案:
- 算法的公共步骤被分解成一个抽象类,抽象(未实现的)基本操作表示算法的可定制部分。
- 子类为每个步骤提供不同的实现。
- 模板方法模式使用继承+可覆盖方法来改变算法的一部分
- 而策略模式使用委托来改变整个算法(接口和特别多态)。
- 模板方法广泛应用于框架中
- 框架实现了算法的不变性
- 客户端定制为算法提供了专门的步骤
- 原则:“不要叫我们,我们会叫你”。
Whitebox还是Blackbox框架?
例子
(3) Iterator
-
问题:客户机需要统一的策略来访问容器中的所有元素,与容器类型无关
- 客户端希望遍历被放入 容器/集合类的一组ADT对象,无需关心容器的具体类型。也就是说,不管对象被放进哪里,都应该提供同样的遍历方式 。
-
解决方案:用于迭代的策略模式
-
结果:
-
隐藏了底层容器的内部实现
-
支持具有统一接口的多重遍历策略
-
易于更改容器类型
-
促进了程序各部分之间的通信
-
模式结构
- 抽象迭代器类定义遍历协议
- 具体迭代器子类 foreach遍历聚合类
- 迭代器实例创建迭代器对象的实例
- 聚合类实例保持对迭代器对象的引用
-
Iterable接口:实现该接口的集合对象是可迭代遍历的
-
Iterator接口:迭代器
-
Iterator pattern:让自己的集合类实现Iterable接口,并实现自己的 独特Iterator迭代器(hasNext, next, remove),允许客户端利用这 个迭代器进行显式或隐式的迭代遍历:
得到一个迭代器
- 定义用于创建迭代器的接口,但允许集合实现决定创建哪个迭代器。
一个迭代器模式的例子
总结
-
结构型模式
- Adapter允许具有不兼容接口的类一起工作,方法是将其自己的接口包装在已经存在的类的接口上。
- Decorator在对象的现有方法中动态添加/覆盖行为。
- Facade为大量代码提供了一个简化的接口。
-
行为模式
- Strategy允许在运行时动态地选择一系列算法中的一个。
- Template方法将算法的框架定义为一个抽象类,允许它的子类提供具体的行为。
- Iterator按顺序访问对象的元素,而不公开其底层表示。