跳转到内容

软件设计模式

来自维基教科书,开放世界中的开放书籍


如果你还记得,软件工程师使用一种叫做 UML 的通用语言。如果我们用语言的类比,那么设计模式就是我们文化中共享的通用故事,比如童话故事。它们是关于软件设计中常见问题及其解决方案的故事。正如年幼的孩子从童话故事中学习善恶,初级软件工程师从好的设计(设计模式)和不好的设计(反模式)中学习。

设计模式的定义

[编辑 | 编辑源代码]

在软件工程中,设计模式是针对软件设计中常见问题的一种通用可复用解决方案。设计模式不是可以直接转换为代码的最终设计,它是一种描述或模板,说明如何解决可以在许多不同情况下使用的问题。面向对象的设计模式通常显示类或对象之间的关系和交互,而没有指定所涉及的最终应用程序类或对象。

设计模式存在于模块和互连的领域。在更高层次上,存在范围更大的架构模式,通常描述整个系统遵循的总体模式。[1]

设计模式有很多类型:结构型模式解决与正在开发的应用程序的高级结构相关的关注点。计算模式解决与识别关键计算相关的关注点。算法策略模式解决与描述如何在计算平台上利用应用程序特征的高级策略相关的关注点。实现策略模式解决与实现源代码以支持程序本身的组织方式以及特定于并行编程的常见数据结构相关的关注点。执行模式解决与支持应用程序的执行相关的关注点,包括执行任务流的策略以及支持任务之间同步的构建块。

设计模式的示例

[编辑 | 编辑源代码]

查看具体示例时,设计模式最容易理解。对于初学者来说,以下十种模式就足够了。但是,你应该养成学习下面提到的其他一些模式的习惯。你了解的模式越多越好。

工厂方法

[编辑 | 编辑源代码]

工厂模式根据某个参数(通常是字符串)从一组类似的类中创建一个对象。例如,在 Java 中创建 MessageDigest 对象

MessageDigest md = MessageDigest.getInstance("SHA-1");

例如,如果将参数更改为 "MD5",则会获得一个根据 MD5 算法计算消息摘要的对象。使用参数的优点是,更改算法不需要重新编译代码。此模式的其他示例是使用 Class.forName("jdbc.idbDriver") 加载 Java 中的数据库连接驱动程序,这虽然是一种非常奇怪的语法,但基本原理相同。

抽象工厂

[编辑 | 编辑源代码]

工厂模式只影响一个类,而抽象工厂模式影响一整组类。Java 中的一个众所周知的例子是 Swing Look-and-Feels

UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");

这看起来与工厂模式非常相似,但区别在于现在正在加载的每个 Swing 类都会受到影响,而不仅仅是一个类。

单例模式

[编辑 | 编辑源代码]

这是最危险的设计模式之一,如果有疑问,就不要使用它。它的主要目的是保证特定对象的实例只有一个。可能的应用是打印机管理器或数据库连接管理器。当需要控制对有限资源的访问时,它很有用。

迭代器

[编辑 | 编辑源代码]

如今,迭代器模式非常简单:它允许你遍历一个对象列表,从开头开始,依次遍历列表中的每个元素,直到到达末尾。

模板方法

[编辑 | 编辑源代码]

模板方法模式也很简单:只要你定义了一个抽象类,该类强制其子类实现某些方法,你就在使用模板模式的简单形式。

命令模式

[编辑 | 编辑源代码]

要了解命令模式背后的理念,请考虑以下餐厅示例:顾客去餐厅点餐。服务员接受订单(命令,在本例中)并将订单交给厨房的厨师。在厨房里,命令被执行,并且根据命令准备不同的食物或饮料。

观察者模式

[编辑 | 编辑源代码]

观察者模式是最流行的模式之一,并且有许多变体。假设您在电子表格中有一个表格。这些数据可以以表格形式显示,也可以以图表或直方图的形式显示。如果基础数据发生变化,不仅表格视图需要更改,而且您还期望直方图发生变化。为了传达这些变化,您可以使用观察者模式:基础数据是可观察对象,表格视图和直方图视图是观察者,它们观察可观察对象。观察者模式的另一个例子是 Swing 中的按钮:这里 JButton 是可观察对象,如果按钮发生了一些事情(通常意味着它被用户按下),那么监听器(观察者)就会收到通知。

组合模式

[编辑 | 编辑源代码]

组合模式非常普遍。基本上,它是一个列表,它可能包含对象,也可能包含列表。一个典型的例子是文件系统,它可能由目录和文件组成。这里目录可能包含文件,也可能包含其他目录。组合模式的其他例子是包含其他菜单的菜单,或者在用户管理中,人们经常有用户和组,其中组可能包含用户,也可能包含其他组。

状态模式

[编辑 | 编辑源代码]

在状态模式中,对象的内部状态会影响其行为。假设您有一些绘图程序,您希望能够绘制直线和虚线。与其为直线创建不同的类,不如创建一个 Line 类,它有一个名为“dotted”或“straight”的内部状态,并且根据这个内部状态,绘制虚线或直线。例如,当通过“setFont()”设置字体或通过“setColor()”设置颜色时,Java 也隐式地使用了这种模式。

代理模式

[编辑 | 编辑源代码]

代理模式背后的想法是我们有一些复杂的对象,我们需要让它变得更简单。一个典型的应用是一个存在于另一台机器上的对象,但您想给用户一种印象,就好像用户正在处理本地对象一样。另一个应用是当一个对象需要很长时间才能创建(比如加载大型图像/视频),但实际上可能永远不需要这个对象。在这种情况下,代理代表对象,直到需要它为止。

模式在实践中

[编辑 | 编辑源代码]

设计模式可以通过提供经过测试和验证的开发范式来加快开发过程。有效的软件设计需要考虑在实现后期才可能出现的问题。重用设计模式有助于防止可能导致重大问题的细微问题,并且它还提高了熟悉这些模式的编码人员和架构师的代码可读性。除此之外,模式允许开发人员使用众所周知、易于理解的软件交互名称进行交流。

为了实现灵活性,设计模式通常会引入额外的间接层,这在某些情况下可能会使最终的设计变得复杂并影响应用程序的性能。

根据定义,模式必须在新应用程序中重新编程才能使用。由于某些作者认为这与组件提供的软件重用相比是一步倒退,因此研究人员一直在努力将模式转变为组件。Meyer 和 Arnout 能够完全或部分组件化他们尝试的三分之二的模式。[2]

模式的分类和列表

[编辑 | 编辑源代码]

设计模式最初被分为三类:创建型模式、结构型模式和行为型模式,并使用委托、聚合和咨询的概念进行描述。另一种分类也引入了架构设计模式的概念,它可以应用于软件的架构级别,例如模型-视图-控制器模式。以下模式取自设计模式[3]代码大全[4],除非另有说明。


创建型模式

[编辑 | 编辑源代码]
  • 抽象工厂:提供一个接口用于创建相关或依赖对象的家族,而无需指定它们的具体类。
  • 建造者:将复杂对象的构造与其表示分离,允许相同的构造过程创建不同的表示。
  • 工厂方法:定义一个用于创建对象的接口,但让子类决定要实例化的类。工厂方法允许类将实例化推迟到子类。
  • 延迟初始化:延迟创建对象、计算值或其他一些昂贵过程的策略,直到第一次需要时才执行。[5]
  • 多例模式:确保一个类只有命名实例,并提供对它们的全局访问点。
  • 对象池:通过回收不再使用的对象来避免昂贵的资源获取和释放。可以被认为是连接池和线程池模式的泛化。
  • 原型模式:使用原型实例指定要创建的对象类型,并通过复制该原型来创建新对象。
  • 资源获取即初始化:通过将资源绑定到合适的对象的生存期来确保资源被正确释放。
  • 单例模式:确保一个类只有一个实例,并提供对它的全局访问点。


结构型模式

[编辑 | 编辑源代码]
  • 适配器模式或包装器模式:将一个类的接口转换为客户端所期望的另一个接口。适配器模式允许原本因接口不兼容而无法一起工作的类协同工作。
  • 桥接模式:将抽象与实现解耦,允许两者独立变化。
  • 组合模式:将对象组合成树形结构,以表示部分-整体层次结构。组合模式允许客户端以统一的方式处理单个对象和对象的组合。
  • 装饰器模式:动态地向对象附加额外的职责,同时保持相同的接口。装饰器模式为扩展功能提供了比子类化更灵活的替代方案。
  • 外观模式:为子系统中的一组接口提供统一的接口。外观模式定义了一个更高级别的接口,使子系统更容易使用。
  • 前端控制器模式:为子系统中的一组接口提供统一的接口。前端控制器模式定义了一个更高级别的接口,使子系统更容易使用。
  • 享元模式:使用共享来有效地支持大量细粒度的对象。
  • 代理模式:为另一个对象提供一个代理或占位符,以控制对它的访问。


行为型模式

[编辑 | 编辑源代码]
  • 黑板模式:泛化的观察者模式,允许多个读取器和写入器。在系统范围内传达信息。
  • 责任链模式:通过让多个对象有机会处理请求,避免将请求发送者与其接收者耦合。将接收对象链接起来,并将请求沿着链传递,直到某个对象处理它为止。
  • 命令模式:将请求封装成一个对象,从而允许您使用不同的请求参数化客户端,对请求进行排队或记录,并支持可撤消的操作。
  • 解释器模式:给定一种语言,定义其语法的表示以及使用该表示来解释语言中的句子的解释器。
  • 迭代器模式:提供一种以顺序方式访问聚合对象元素的方法,而无需公开其底层表示。
  • 中介者模式:定义一个对象,它封装了一组对象如何交互。中介者模式通过防止对象显式地相互引用来促进松耦合,并且允许您独立地改变它们的交互。
  • 备忘录模式:在不违反封装的情况下,捕获并外部化对象的内部状态,允许对象稍后恢复到此状态。
  • 空对象模式:通过提供一个默认对象来避免空引用。
  • 观察者模式或发布/订阅模式:定义对象之间的一对多依赖关系,其中一个对象的狀態更改会导致其所有依赖项被自动通知并更新。
  • 服务员模式:为一组类定义通用功能。
  • 规格模式:以布尔方式重新组合业务逻辑。
  • 状态:允许对象在内部状态改变时改变其行为。该对象将看起来像改变了其类。
  • 策略:定义一系列算法,封装每个算法,并使它们可以互换。策略使算法能够独立于使用它的客户端而变化。
  • 模板方法:在操作中定义算法的骨架,将一些步骤推迟到子类中。模板方法允许子类重新定义算法的某些步骤,而无需更改算法的结构。
  • 访问者:表示要对对象结构元素执行的操作。访问者允许你定义新的操作,而无需更改其操作的元素的类。


并发模式

[编辑 | 编辑源代码]

以下大多数并发模式取自POSA2[6]

  • 活动对象:将方法执行与驻留在自身控制线程中的方法调用分离。目标是通过使用异步方法调用和用于处理请求的调度程序来引入并发。
  • 推迟:仅在对象处于特定状态时才对对象执行操作。
  • 绑定属性:组合多个观察者以强制不同对象中的属性以某种方式同步或协调。[7]
  • 消息传递模式:消息传递设计模式 (MDP) 允许组件和应用程序之间交换信息(即消息)。
  • 双重检查锁定:通过首先以不安全的方式测试锁定条件(“锁定提示”)来减少获取锁的开销;只有在成功的情况下才会进行实际锁定。在某些语言/硬件组合中实现时可能不安全。因此,有时它被认为是一种反模式。
  • 基于事件的异步:解决多线程程序中异步模式出现的问题。[8]
  • 保护性暂停:管理需要获取锁和满足先决条件才能执行的操作。
  • 锁:一个线程对资源进行“锁定”,阻止其他线程访问或修改它。[9][5]
  • 监视器对象:其方法受互斥影响的对象,从而防止多个对象错误地尝试同时使用它。
  • 反应器:反应器对象为必须同步处理的资源提供异步接口。
  • 读写锁:允许对对象进行并发读取访问,但写入操作需要独占访问。
  • 调度程序:明确控制线程何时可以执行单线程代码。
  • 线程池:创建一定数量的线程来执行一定数量的任务,这些任务通常组织在一个队列中。通常,任务比线程多得多。可以看作是对象池模式的特例。
  • 线程特定存储:特定于线程的静态或“全局”内存。

数据访问模式

[编辑 | 编辑源代码]

另一个模式在数据访问模式领域有着广泛应用的有趣领域。Clifton Nock [10] 列出了以下模式

  • ORM 模式:域对象工厂、对象/关系映射、更新工厂
  • 资源管理模式:资源池、资源计时器、重试器、分页迭代器
  • 缓存模式:缓存访问器、按需缓存、预先加载缓存、缓存收集器、缓存复制器
  • 并发模式:事务、乐观锁、悲观锁


企业模式

[编辑 | 编辑源代码]

如果你使用 J2EE 或 .Net 企业应用程序,出现的错误和解决方案是相似的。这些解决方案是企业模式。核心 J2EE 模式[11] 列出了这些模式

  • 表示层模式:拦截过滤器、前端控制器、视图帮助程序、复合视图、服务到工作者、调度程序视图
  • 业务层模式:业务委托、值对象、会话外观、复合实体、值对象组装器、值列表处理程序、服务定位器
  • 集成层模式:数据访问对象、服务激活器


实时模式

[编辑 | 编辑源代码]

最后,在实时和嵌入式软件开发领域,已经识别出大量模式。[12][13] 在他的书实时设计模式:实时系统的健壮可扩展架构[14][15] Bruce Powel Douglass 列出了一些非常有趣的模式

  • 架构模式:分层模式、通道架构模式、基于组件的架构、递归包含模式和分层控制模式、微内核架构模式、虚拟机模式
  • 并发模式:消息队列模式、中断模式、保护调用模式、会合模式、循环执行模式、循环模式
  • 内存模式:静态分配模式、池分配模式、固定大小缓冲区模式、智能指针模式、垃圾收集模式、垃圾压缩模式
  • 资源模式:临界区模式、优先级继承模式、优先级上限模式、同时锁定模式、有序锁定模式
  • 分布模式:共享内存模式、远程方法调用模式、观察者模式、数据总线模式、代理模式、代理模式
  • 安全性和可靠性模式:监视器-执行器模式、健全性检查模式、看门狗模式、安全执行模式、保护的单通道模式、同构冗余模式、三模冗余模式、异构冗余模式


人们还努力在特定领域对设计模式进行编码,包括使用现有设计模式以及特定于领域的模式。例如,用户界面设计模式,[16] 信息可视化 [17],安全设计[18],“安全可用性”[19],Web 设计 [20] 和商业模式设计。[21] 每年举办的编程模式语言会议记录[22] 包含许多特定于领域的模式示例。

记录和描述模式

[编辑 | 编辑源代码]

假设你发现了一个新的设计模式。或者你的朋友想向你解释她在模式库中找到的这个很酷的模式。我们如何描述模式?没有单一的标准格式来记录设计模式。相反,不同的模式作者使用了各种不同的格式。[23] 但是,根据 Martin Fowler,某些模式形式比其他形式更有名,因此成为新模式写作工作中常见的起点。[24] 设计模式一书使用了一种常用的文档格式。[3] 它包含以下部分

  • 模式名称和分类:有助于识别和引用模式的描述性和唯一名称。
  • 意图:对模式背后的目标和使用它的原因的描述。
  • 别名:模式的其他名称。
  • 动机(驱动力):一个包含问题和可以使用此模式的上下文的场景。
  • 适用性:可以使用此模式的情况;模式的上下文。
  • 结构:模式的图形化表示。类图和交互图可以用于此目的。
  • 参与者:模式中使用的类和对象的列表以及它们在设计中的角色。
  • 协作:对模式中使用的类和对象如何相互交互的描述。
  • 后果:使用模式引起的结果、副作用和权衡的描述。
  • 实现:模式的实现描述;模式的解决方案部分。
  • 示例代码:模式如何在编程语言中使用的示例。
  • 已知用途:模式的真实使用示例。
  • 相关模式:与模式有一定关系的其他模式;模式与类似模式之间差异的讨论。

结构、参与者和协作部分尤其令人关注。这些部分描述了一个设计动机:一个开发者复制并适应其特定设计以解决设计模式描述的重复问题的原型微架构。微架构是一组程序组成部分(例如,类、方法...)及其关系。开发者通过在其设计中引入此原型微架构来使用设计模式,这意味着他们设计中的微架构将具有与所选设计动机相似的结构和组织。

模式起源于克里斯托弗·亚历山大(1977/79)提出的一个建筑概念。1987 年,肯特·贝克和沃德·坎宁安开始尝试将模式应用于编程,并在当年的 OOPSLA 会议上展示了他们的成果。[25][26] 在接下来的几年里,贝克、坎宁安和其他人继续进行这项工作。

设计模式在 1994 年“四人帮”(Gamma 等)出版的《设计模式:可复用面向对象软件元素》一书之后在计算机科学领域获得了普及。[3] 同年,第一次编程模式语言大会召开,次年,波特兰模式库建立,用于设计模式的文档记录。


参考文献

[编辑 | 编辑源代码]
  1. Martin, Robert C. "设计原则和设计模式" (PDF). Retrieved 2000. {{cite web}}: Check date values in: |accessdate= (help)
  2. Meyer, Bertrand (2006). "组件化:访问者示例" (PDF). IEEE Computer. IEEE. 39 (7): 23–30. {{cite journal}}: Unknown parameter |coauthors= ignored (|author= suggested) (help); Unknown parameter |month= ignored (help)
  3. a b c Gamma, Erich (1995). 设计模式:可复用面向对象软件元素. Addison-Wesley. ISBN 0-201-63361-2. {{cite book}}: Unknown parameter |coauthors= ignored (|author= suggested) (help)
  4. McConnell, Steve (2004). "设计在建设中". 代码大全 (第二版). Microsoft Press. p. 104. ISBN 978-0735619678. 表 5.1 常用设计模式 {{cite book}}: Unknown parameter |month= ignored (help)
  5. a b Fowler, Martin (2002). 企业应用架构模式. Addison-Wesley. ISBN 978-0321127426.
  6. , 除非另有说明。 Schmidt, Douglas C. (2000). 面向模式的软件架构,第二卷:并发和联网对象的模式. John Wiley & Sons. ISBN 0-471-60695-2. {{cite book}}: Unknown parameter |coauthors= ignored (|author= suggested) (help)
  7. http://c2.com/cgi/wiki?BindingProperties
  8. Christian Nagel, Bill Evjen, Jay Glynn, Karli Watson, and Morgan Skinner (2008). "基于事件的异步模式". 专业 C# 2008. Wiley. pp. 570–571. ISBN 0470191376. {{cite book}}: Unknown parameter |isbn13= ignored (help)CS1 maint: multiple names: authors list (link)
  9. http://c2.com/cgi/wiki?LockPattern
  10. Nock, Clifton (2003). 数据访问模式:面向对象应用程序中的数据库交互. Addison Wesley. ISBN 0131401572.
  11. Alur, Deepak (2003). 核心 J2EE 模式:最佳实践和设计策略(第 2 版). Prentice Hall. ISBN 0131422464. {{cite book}}: 未知参数 |coauthors= 被忽略 (|author= 建议) (帮助); 未知参数 |month= 被忽略 (帮助)
  12. "STL 设计模式 II". EventHelix.com Inc. 检索于 2011-03-08.
  13. "嵌入式设计模式". EventHelix.com Inc. 检索于 2011-03-08.
  14. Douglass, Bruce Powel (2002). 实时设计模式:实时系统健壮可扩展架构. Addison Wesley. ISBN 0201699567.
  15. Douglass, Bruce Powel (1999). 艰苦时光:使用 UML、对象、框架和模式开发实时系统. Addison Wesley. ISBN 0201498375. {{cite book}}: |title= 位置 65 处的换行符 (帮助)
  16. Laakso, Sari A. (2003-09-16). "用户界面设计模式的集合". 赫尔辛基大学,计算机科学系. 检索于 2008-01-31.
  17. Heer, J. (2006). "信息可视化的软件设计模式". IEEE 可视化与计算机图形学汇刊. 12 (5): 853. doi:10.1109/TVCG.2006.178. PMID 17080809. {{cite journal}}: 未知参数 |coauthors= 被忽略 (|author= 建议) (帮助)
  18. Chad Dougherty; 等 (2009). 安全设计模式 (PDF). 卡内基梅隆大学软件工程研究所. {{cite book}}: |author= 中显式使用等 (帮助); 无效 |nopp=Technical Report (帮助); 未知参数 |nopp= 被忽略 (|no-pp= 建议) (帮助)
  19. Simson L. Garfinkel (2005). 同时安全且易用的计算机系统的设计原则和模式. 麻省理工学院. {{cite book}}: 无效 |nopp=PhD Thesis (帮助); 未知参数 |nopp= 被忽略 (|no-pp= 建议) (帮助)
  20. "雅虎!设计模式库". 检索于 2008-01-31.
  21. "如何将您的商业模式设计为精益创业?". 检索于 2010-01-06.
  22. 编程模式语言,会议记录(每年,1994—)[1]
  23. Gabriel, Dick. "模式定义". 存档于 原始 on 2007-02-09. 检索于 2007-03-06.
  24. Fowler, Martin (2006-08-01). "编写软件模式". 检索于 2007-03-06.
  25. Smith, Reid (1987年10月). "面板设计方法". OOPSLA '87 附录. OOPSLA '87. doi:10.1145/62138.62151. , "沃德告诫不要在所谓的'高级向导'中要求过多的编程。他指出,书面'模式语言'可以显著提高抽象的选择和应用。他提出了一种'将设计和实现负担进行根本性转变'的新方法,这种方法基于对克里斯托弗·亚历山大的模式语言作品的改编,而泰克特罗尼克斯开发的面向编程的模式语言极大地帮助了他们的软件开发工作。"
  26. Beck, Kent; Ward Cunningham (1987年9月). "使用模式语言进行面向对象编程". OOPSLA '87 面向对象编程规范和设计研讨会. OOPSLA '87. http://c2.com/doc/oopsla87.html. 检索于 2006-05-26. 

进一步阅读

[编辑 | 编辑源代码]
书籍
网站
[编辑 | 编辑源代码]
华夏公益教科书