本文力求在 15 分钟内,通过
范例
和类比
,让你对面向对象的 23 种设计模式造成提纲挈领的意识,从而让咱们在面临代码设计问题时更加成竹在胸。
单刀直入
咱们直奔主题,分类出现 23 种设计模式的庐山真面目:
创立型 (5) Creational |
结构型 (7) Structural |
行为型 (11) Behavioral |
---|---|---|
工厂办法 Factory method 形象工厂 Abstract factory 建造者 Builder 原型 Prototype 单例 SingleTon |
适配器 Adapter 桥接 Bridge 组合 Composite 装璜 Decorator 外观 Facade 享元 Flyweight 代理 Proxy |
责任链 Chain of responsibility 命令 Command 解释器 Interpreter 迭代器 Iterator 中介 Mediator 备忘录 Memento 观察者 Observer 状态 State 策略 Strategy 模板办法 Template method 访问者 Visitor |
这 23 种设计模式源于 GoF 所著的 ”Design Patterns – Elements of Reusable Object-Oriented Software” 一书(也有将该书间接简称为 GoF),译著为“设计模式:可复用面向对象软件的根底”。原书将这 23 种设计模式分为三类:
- 创立型 蕴含 5 种模式,波及对象 / 对象组合的创立构建。
- 结构性 蕴含 7 种模式,波及对象 / 类之间的关系。
- 行为型 蕴含 11 种模式,波及对象 / 类的行为、状态、流程。
从该书的题目咱们能够理解到,设计模式是一个 面向对象 开发方法下的概念,是解决 代码设计 / 软件架构 问题的 可复用 的元素,同时是 根本元素(elements)。援用原书的例子,咱们大家所熟识的 MVC 模式,Model-View-Controller,就能够解构为几种设计模式的组合演变,比方能够在 View 和 Model 的关系中看到观察者模式 Observer、组合模式 Composite、装璜模式 Decorator,在 Controller 中发现策略模式的影子。通过对 23 种根底模式的有机利用和联合,能够进一步演化出更简单的软件架构。限于篇幅,本文不会解说每种设计模式的定义和背景,读者能够参考设计模式简介来学习定义。
设计模式的 UML、类比和范例
这个局部,咱们逐渐从尝鲜到类比,深刻了解一些比拟常见乏味的设计模式的 UML 及其经典实例。GoF 原书中也举荐学习者从“模式怎么互相关联”以及“钻研目标类似的模式“登程来学习和抉择设计模式。首先看看最简略常见的 策略模式
和另一个同属 行为型模式 的状态模式
:
策略模式 Strategy | 状态模式 State | |
---|---|---|
UML | ||
范例 | – Comparator#compare() 和 Collections#sort() – Spring Security: PasswordEncoder |
– 规范范例: javax.faces.lifecycle.LifeCycle#execute() – 形似样例:Java Thread State, ExoPlayer |
概述 | 让内部对算法的互相替换无感 | 容许一个对象依据 外部状态 扭转行为 |
关键字 | Strategy, rule | State, switch, phase, lifecycle |
外围角色 | Strategy | State |
策略模式和状态模式在 UML 图形上十分相像,他们之间的次要区别如下:
- 状态对象能够持有上下文对象(调用方),但策略模式个别存在这种依赖。
- 状态模式能够在彼此之间进行跳转替换,比方调用了播放器的
play
办法,那么状态可能从stop
->playing
,这个操作能够用状态对象实现。 - 一个策略和调用方的关系(依赖)可能弱于状态和上下文对象的关系(持有、属性)。
- 策略的不同可能只影响一个行为,然而状态的不同影响状态持有对象行为的方方面面。
整体上策略模式要比状态模式更加扼要易懂,利用场景更广,在大型项目中的利用也随处可见。而状态模式尽管也是对常见概念的形象,其利用却绝对无限,其起因可能是,在更多的状况下,把行为的差别定义在不同的状态中,可能并非合乎直觉的操作:与其把状态也定义为对象承载行为,不如把状态定义为一个标记,间接用 if
或switch
判断来的间接。或者换言之,大多数状况下,问题还没有简单到要用状态模式的水平。
借助这种比照的视角,咱们来学习更多模式。先看看以下三种结构型设计模式:
适配器 | 桥接模式 | 外观 | |
---|---|---|---|
UML | |||
范例 | RecyclerView.Adapter | 范例比拟少: – Collections#newSetFromMap() – (ADB?),如 Spring 中 Service 和 Repository 的关系 |
十分常见,如:Facades, FacesContext, ExternalContext, DataSource#getConnection() |
概述 | 将一个类的接口转换成满足另一个要求的接口 | 将形象局部与它的实现局部拆散 | 为子系统中的一组接口提供一个统一易用的界面 |
关键字 | Adatper | Wrapper | Context |
外围角色 | Adpter, Adaptee | Bridge | Facade |
适配器模式、桥接模式和外观模式同属结构型设计模式,他们三者概念上很相像,都是通过建设接口来为类的办法建设或重构关系,比方,仿佛咱们用外观的视角去解释适配器,也能解释的通,Adapter 就是在帮忙 Adaptee 建设对立界面,或者建设桥梁。
设计模式就是这样,非要较真,所有的设计模式都大同小异(至多在一个类型之内),这是学习设计模式的一个误区。回到下面的三个设计模式上,他们的外围区别更多体现在机会和出发点上:适配器 Adapter 强调兼容性,桥接 Bridge 强调形象与实现的拆散,而外观 Facade 强调简化复杂性。咱们分辨这些模式也应该从用意登程来看。
Spring 的三层构造也交融体现了 Facade 和 Bridge 的设计,Service 和 Repository 之间并重体现 Bridge 模式理念,而 Controller 和 Service 之间更像 Facade 模式:Controller 整合 Service,对外提供 API:
上面咱们再看几种常见的行为型模式的类比剖析:
代理 | 装璜 | 中介 | |
---|---|---|---|
UML | |||
范例 | – Java Reflect API: Proxy – Java EJB: Enterprise JavaBean, JavaX Inject, JavaX PersistenceContext – ActivityManager 和 ActivityManagerService – PerformanceInspectionService 和 PerformanceTestManagementService |
– Java IO: GZIPOutputStream and OutputStream, Reader and BufferedReader) – java.util.Collections, checkedXXX(), synchronizedXXX() 和 unmodifiableXXX() 系列办法,拓展汇合 – HttpServletRequestWrapper and HttpServletResponseWrapper – JScrollPane |
– Java Message Service, JMS by Oracle – java.util.Timer (all scheduleXXX() methods), java.util.concurrent.ExecutorService (the invokeXXX() and submit() methods) |
概述 | 通过代理来管制对一个对象的拜访 | 动静地给一个对象增加性能 | 封装对象之间的交互(传话筒) |
关键字 | Delegate | Wrapper | MessageQueue, Dispatcher |
外围角色 | Proxy | Decorator | Mediator |
这里,从类之间关系上看,代理和装璜更为类似,而中介则不同,它只是名字上和代理相近。对于代理 (拜访和管制) 和装璜(加强和扩大)的辨别,同样能够从目标和用意的角度辨别。以代理来为例,它的首要作用是建设拜访通道,比方安卓中,利用和零碎之间用 Binder 来进行 IPC,而在利用过程和零碎过程间,为了这种 IPC 调用,大量利用了代理模式,名为 Proxy 的对象随处可见。而在设计 Hydra Lab 的过程中,为了让测试用户能不便的在测试实例中通过 SDK 拜访一些 Hydra Lab Test Agent 的服务办法,咱们也利用了一个扼要的动态代理来实现这种不同环境下的拜访。
在代理模式下,有了拜访通道,天然就能够做到对通信的管制,比方基于权限的、或是基于格局验证的。而装璜模式着眼于 加强、扩大,比方 BufferedRead 对于 Reader 的加强。从这个角度讲,一个类如果叫 AuthWrapper 就会比拟奇怪,AuthProxy 则更常见一些,因为受权这种操作显著更强调管制。当然这取决于具体情境。
中介其实是很宽泛的概念,解耦通信的单方或多方,比拟炽热的各类 MQ 框架其实是这个模式的一个衍生。
观察者 | 访问者 | |
---|---|---|
UML | ||
范例 | – java.util.Observer, Observable – java.util.EventListener – ReactiveX Interface Observer |
– AnnotationValueVisitor – ElementVisitor – TypeVisitor – SimpleFileVisitor – VisitCallback – ClassVisitor (ASM 9.4) |
概述 | 对个观察者监听一个主题对象 | 示意一种对某对象中各元素的只读操作 |
关键字 | Observable, Observer, Subject, Subscription |
Visitor |
外围角色 | Observer, Subject | Visitor, Element |
这两个模式之间在实现上其实并没有太多分割。但二者都是想去“读”,不会间接扭转被读对象的状态。观察者通过订阅监听的形式被动地读,而访问者是被动视角,以一种独特的形式读。和观察者很相近的“Listener”,是更常见的概念,更轻量,因此也更宽泛。
责任链 | 备忘录 | |
---|---|---|
UML | ||
范例 | – OkHttp Interceptors – java.util.logging.Logger – javax.servlet.Filter |
– Activity#onSaveInstanceState(…)) – Java Serializable |
概述 | 建设解决链条传递申请 | 捕捉对象状态并保留,以备状态复原 |
关键字 | Chain, Interceptor, Filter, proceed, Response | State, Lifecycle, Context |
外围角色 | Handler | Memonto, Originator, Caretaker |
责任链和备忘录模式尽管用意和设计上都不雷同,但二者都有十分浓重的 IoC 管制反转的滋味,和生命周期的设计分割严密。玩游戏的同学对备忘录模式最容易建设了解,一个存档就是一个长久化的 State,游戏自身的存读档服务作为 caretaker,帮你保障你肝的进度不会徒劳。所以备忘录模式其实十分的常见,软件世界里俯拾皆是。
命令 | 解释器 | |
---|---|---|
UML | ||
范例 | – IShellOutputReceiver – Java Runnable |
– java.util.Pattern – java.text.Normalizer – java.text.Format – javax.el.ELResolver |
概述 | 将申请封装为对象,从而不便参数化和申请队列治理 | 定义文法和示意形式 |
关键字 | Executor | Expression |
外围角色 | Command, Receiver, Invoker(Executor) | Interpretor, Expression |
下面两者也无奈间接类比,然而当二者合体,命令的解释和执行零打碎敲,一个脚本语言的 c 执行器雏形就诞生了。这里的命令模式其实比“命令”自身在设计上有更周全的思考,它还包含了对执行后果的接管接口的预留。
形象工厂 | 工厂办法 | |
---|---|---|
UML | ||
范例 | – DocumentBuilderFactory(JavaX) – TransformerFactory(JavaX) – XPathFactory(JavaX) – BeanFactory#getBeanProvider(Spring) |
java.util.Calendar#getInstance() |
概述 | 将一个类的接口转换成满足另一个要求的接口 | 由工厂的子类决定创立的实例对象 |
关键字 | Factory, new…, create… | Factory, newInstance, Creator |
外围角色 | AbstractFactory | Creator |
其余模式还包含:建筑者模式,原型模式,享元(相似多例),单例;组合;模板办法,迭代器。这些模式或是不罕用,或是过于罕用常见,且都比较简单,限于篇幅本文不再一一详述。
通过这个类比学习的过程,咱们可能会逐渐感触到,设计模式的重点并不在于类之间关系的严格定义、列举和排布,无意义的辩论、论证会陷入“把设计模式当作一个严格的学术实践”的 误区
。更多的,咱们应该从问题的用意登程,发散思考解决方案中可能蕴含的设计元素,而后依据理论状况精简到正当的规模。
所以咱们不用纠结于相近的两种模式的严格界定和辨别,比方,无需辩驳一种实现到底是用的代理还是装璜,而是了解这两种模式的 看问题的角度
和用意
,死记硬背,灵便组合使用:如果你强调的角度是性能拓展,那设计方案就是装璜;如果你强调的是访问控制,那就是代理。很多初学者感觉很多模式很类似,感到多余,这是很失常的感触和学习阶段;随着更多利用和实战,你会成长和洞察更多模式的意义;起初你曾经成为设计巨匠,灵活运用设计模式、AOP、函数式、算法乃至 ML 解决各类问题,讲述和推动计划的实现,设计模式的探讨和答辩只不过是茶余饭后的谈资。这一点,在原书“怎么抉择设计模式”章节中,也有提及。
总结来讲,初学设计模式,关注点能够放在:
- 这个设计模式解决什么类型的问题,
用意
是什么,以及它如何对概念进行形象(要害角色
)和解决(接口、关系
)的。 - 用设计模式作为大家沟通软件设计的
语言
,把握这些术语,缩小沟通老本。
如何学习和应用设计模式
本局部内容源自 GoF 原书中 1.8 章的内容“怎么应用设计模式”,精简了原书的 7 步为 6 步,并去除了翻译腔:
- 浏览一遍该模式,把握要害因素
这个模式的名字是什么,用意是什么,外面的要害角色是什么,常见的关键词? - 回头去钻研构造局部、参与者局部和合作局部
进一步理解角色的职责和关系,有哪些接口,以及模式的适用性:这个模式更适宜解决什么类型的问题? - 看看示例代码
例子能让咱们理解模式解决的理论问题,成为咱们实现的参考。 - 参考模式中的命名办法
比方,在 Strategy 模式中,你能够间接给算法命名开端加上 Strategy 来体现这个模式;再比方,能够用 create 作为办法的结尾前缀来凸显工厂办法。 - 定义类和接口
选定好模式、实现命名后,下一步能够建设好类与接口之间的继承 / 实现关系,定义代表数据和对象援用的实例变量。 - 实现模式
开始根据模式实现解决方案。
设计模式的引入是带有肯定老本的,学习老本和复杂性的减少就是其中之一,也可能会有性能上的损耗(尽管能够疏忽),但它为架构带来了灵活性,使其更加清晰可保护。接下来,作为拓展浏览,咱们能够探讨一下设计模式的意义,取得更深的了解。
设计模式的意义和批评
谈及为什么须要设计模式时,首先要答复什么设计是好的设计。软件是对事实问题复杂性的形象和治理,Uncle bob 说:“软件应该是可变的”,正如事实世界“惟一不变的就是一直地变动”,软件应该能灵便地应答事实世界的需要。所以咱们会探讨软件架构的 可扩展性、可维护性、高可用、可重用、可移植性 等。如果你只是在编写一个又一个的脚本、一次性工具或者编程练习题,当然不必把问题复杂化。但如果你心愿你的软件有更强的生命力和更广大的前景,那就要庄重看待软件设计,避免代码腐化。
此外,一个人的力量是无限的,如果心愿借助合作来扩充软件的服务范畴、影响力,那么 可读性 也就重要起来。“Good code is like well-written prose”,好代码应该像柔美的散文;至多是自解释的。引述 GoF 的原文,“所有构造良好的面向对象软件体系结构中都蕴含了许多模式 … 外行的设计者晓得:不是解决任何问题都要从头做起 … 这些模式解决特定的设计问题,使面向对象设计更灵便、优雅,最终复用性更好。”所以这里的两层意思就一方面强调了模式对软件设计自身的益处,一方面阐明了这些模式建设了大家在面向对象设计上的共识和交换根底。此外,巨匠们还总结了一些设计准则来框定好的设计。
SOLID 设计准则
只管很多教程将设计准则和设计模式放在一起探讨,暗示设计模式是听从了设计准则,实际上他们并非同出一家。而且设计准则有很多种说法,这里咱们分享 Uncle Bob 提出的最容易记忆的版本,SOLID 准则:
- Single responsibility, 繁多职责准则 SRP:就一个类而言,应该仅有一个引起它变动的起因。
- Open-close, 开闭准则 OCP:软件实体应该对于扩大是凋谢的,对于批改是关闭的。
- Liskov substitution, 里氏代换准则 LSP。子类型必须可能替换掉它们的父类型。把父类实例替换成子类实例,程序行为不应该有变动。
-
Interface segregation, 接口隔离准则 ISP。
- 一个类对另外一个类的依赖性该当是建设在最小的接口上的。
- 客户端程序不应该依赖它不须要的接口办法(性能)。
-
Dependency inverse, 依赖倒转准则 DIP:
- 高层模块不应该依赖低层模块。两个都应该依赖形象。
- 形象不应该依赖细节,细节应该依赖形象。
咱们能够认为这些设计模式是解决设计问题思考的原则,也能够认为他们只是一种理念。正如 Uncle bob 所说:”The SOLID principles are not rules. They are not laws. They are not perfect truths… This is a good principle, it is good advice…”。总之,理解这些能够帮忙咱们把握思考方向,但不能帮咱们解决问题。换言之,对于初学者而言,设计准则可能没有设计模式那样强的实战意义。
批评之声
对于设计模式的 批评,源自于对其创建所处时代的支流编程语言的局限性的挑战,以及对于面向对象自身的质疑。有人认为设计模式的提出反映了 Java 和 C ++ 本身语言个性的缺失;也有认为如果灵活运用 aspect-oriented-programming,就用不着搞进去 23 种之多啰啰嗦嗦的设计模式了。对此,笔者感觉纯正的实践上的对错没那么重要,软件开发是迷信和艺术的联合地带,而设计模式是一个时代开发者思考的精髓积淀,能给咱们带来的不仅是具体计划,更多的是解决问题的思维形式,它们自身就存在于大量的编程实际中,GoF 对他们进行了提炼和综述,这自身就是意义微小的成绩了,更何况他们曾经成为工程师文化的一部分,成为了术语。例如,咱们从 Spring 中既能看到 AOP 的利用、函数式编程的利用,也能看到建造者、工厂模式、策略等等的利用。编程巨匠应该是博学和不拘一格的,代码的艺术正在于灵便和适时的使用,囿于执著信奉而回绝经典或者新知断不可取。
其余常见疑难 FAQ
Q: 设计模式和后续风行的的 Reactive、函数式编程、AOP、IoC 以及 DI 之间的关系是什么?
A: 总体上,这些是不同维度的概念,总结为下表:
概念 | 释义(译) | 领域 |
---|---|---|
设计模式 | Software design pattern | 软件设计的解决方案 |
IoC | 管制反转 Inversion of control | 软件架构层面的一种设计模式 |
DI | 依赖注入 Dependency injection | 一种设计模式 |
AOP | Aspect-oriented programming,面向切面的编程 | 编程范式(面向对象) |
函数式编程 | Functional programming | 编程范式(申明式) |
Reactive | Reactive programming, 响应式编程 | 编程范式(申明式) |
微服务 | Microservices | 一种架构模式(面向服务的架构) |
Q: 是否还有其余设计模式?
A: 有的,随着软件开发实际的演变,有越来越多的设计模式被总结进去,只不过可能还没有一本经典将其整顿入册。比方常见的锁的双重查看,也被认为是一种独立于语言的并发型设计模式。DI 也是设计模式 Concurrency Patterns,其角色包含注入器 Injector,服务 Service,客户端 Client 和接口 Interfaces。DI 也是一种创立型 Creational 设计模式,其用意在于优化类之间依赖关系,因此和整个软件或模块的架构的相关性更亲密。
References
- Design Patterns, Elements of Reusable Object-Oriented Software
- https://en.wikipedia.org/wiki…
- http://butunclebob.com/Articl…
- https://en.wikipedia.org/wiki…
- https://sites.google.com/site…
- https://en.wikipedia.org/wiki…
- https://www.jianshu.com/p/8cb…
- https://coderanch.com/t/99717…
- https://stackoverflow.com/que…
- https://stackoverflow.com/que…
- https://stackoverflow.com/que…
对于我
我是风波信步,目前在微软中国负责研发经理。心愿在这个空间和大家分享交换技术心得,职业生涯,团队和项目管理,趋势动静。旨在畅谈、分享和记录,不拘小节;但也不排除刨根问底、钻牛角尖。
自己酷爱技术和打码,尤其享受用技术解决理论问题的过程和后果;置信创造力是顶级能力,是人价值的放大器。此外,自己专一于软件和代码品质、工程效率和研发能效方面多年,目前在微软和团队一起推动 2023 年新开源的我的项目 Hydra Lab 的欠缺与倒退;欢送和我在开源世界组队打码,造轮子 or 添砖加瓦。