文章目录
设计模式主要是为了改善代码质量,对代码的重用、解耦以及重构给了最佳实践,如下图是我们在掌握设计模式过程中需要掌握和思考的内容概览。
一. 设计原则与思想
1. 面向对象编程基础
掌握类、对象、继承、多态等基本概念。
【设计模式之美】面向对象分析方法论与实现(一):需求分析方法论
【设计模式之美】面向对象分析方法论与实现(二):需求到接口实现的方法论
2. 设计原则与方法论
熟悉SOLID原则(单一职责、开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则)及其在设计模式中的应用。
【设计模式之美】SOLID 原则之一:怎么才算是单一原则、如何取舍单一原则
【设计模式之美】SOLID 原则之二:开闭原则方法论、开闭原则如何取舍
【设计模式之美】SOLID 原则之三:里式替换(LSP)跟多态有何区别?如何理解LSP中子类遵守父类的约定
【设计模式之美】 SOLID 原则之四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?
【设计模式之美】 SOLID 原则之五:依赖反转原则:将代码执行流程交给框架
3. 规范与重构
【设计模式之美】重构二:重构提高代码可测试性、mock替换外部服务
【设计模式之美】重构三:解耦方法论:如何通过封装、抽象、模块化、中间层等解耦代码?
二. 设计模式与范式
1. 创建型模式:解决对象创建问题
包括:单例模式、工厂模式、建造者模式、原型模式。创建者模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。
- 单例模式:用来创建全局唯一的对象。
- 工厂模式:用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。(这里分为简单工厂模式和工厂方法模式,故两种设计模式。)
- 建造者模式:用来创建复杂对象,通过对象中设置可选参数,根据不同逻辑,灵活地创建不同参数的对象。
- 原型模式:针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以达到节省创建时间的目的。
1.1. 单例:全局唯一的对象
1.2. 建造者模式:控制复杂对象的创建
【设计模式之美】【建造型】建造者模式:处理复杂成员变量以及它们之间的关系
1.3. 工厂模式:管理对象
工厂模式包括简单工厂、工厂方法、抽象工厂这 3 种细分模式。其中,简单工厂和工厂方法比较常用,抽象工厂的应用场景比较特殊,所以很少用到,不是我们学习的重点。
根据情况选择模式
- 当每个对象创建比较简单时,使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。
- 当每个对象创建比较复杂时,使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。
详细点说,工厂模式的作用有下面 4 个,这也是判断要不要使用工厂模式最本质的参考标准。
- 封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
- 代码复用:创建代码抽离到独立的工厂类之后可以复用。
- 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。
- 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。
【设计模式之美】【建造型】工厂模式:通过面向接口编程思路,串起业务流程
【设计模式之美】【建造型】工厂模式实战:如何设计一个DI框架;梳理流程,通过面向接口解耦对象创建
1.4. 原型模式-深拷贝与浅拷贝
2. 结构型模式:解决类或对象的组合问题
结构型模式主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题。
结构型模式包括如下七种模式:
- 代理模式:在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能。
- 桥接模式:不常用。
- 装饰器模式:要解决继承关系过于复杂的问题,通过组合来替代继承,给原始类添加增强功能。
- 适配器模式:兼容接口适配问题
- 门面模式:接口的组合,增加接口易用性
- 组合模式:树形数据
- 享元模式:共享对象或变量
2.1. 装饰器模式:通过组合来增强功能 ing
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承,给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这样的需求,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。
【设计模式】继承的替代方式:装饰者模式-动态丰富对象功能,而不是实现大量的继承实例
2.2. 桥接模式(不常用)
桥接模式的代码实现非常简单,但是桥接模式在实际的项目中并没有那么常用,不是学习的重点。
[!桥接模式有两种理解方式]
- 第一种理解方式是“将抽象和实现解耦,让它们能独立开发”。这种理解方式比较特别,应用场景也不多。
- 另一种理解方式更加简单,等同于“组合优于继承”设计原则,这种理解方式更加通用,应用场景比较多。
不管是哪种理解方式,它们的代码结构都是相同的,都是一种类之间的组合关系。
“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是的一套独立的“类库”。“抽象”和“实现”独立开发,通过对象之间的组合关系组装在一起。
2.3. 装饰器模式:通过组合解决复杂的继承关系
- 装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承,给原始类添加增强功能。
- 装饰器模式可以对原始类嵌套使用多个装饰器。此时,装饰器类需要跟原始类继承相同的抽象类或者接口。
2.4. 适配器模式:补救之前的设计
适配器模式是用来做适配的,让原本由于接口不兼容而不能一起工作的类可以一起工作。
适配器模式有两种实现方式:
- 类适配器:使用继承关系来实现
- 对象适配器:使用组合关系来实现。
适配器模式是一种事后的补救策略,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。
有下面这 5 种场景:封装有缺陷的接口设计、统一多个类的接口设计、替换依赖的外部系统、兼容老版本接口、适配不同格式的数据,可以使用适配器模式。
2.5. 门面模式:接口组合满足接口易用性
门面模式原理实现都非常简单,应用场景比较明确。
它通过封装细粒度的接口,提供组合各个细粒度接口的高层次接口,来提高接口的易用性,或者解决性能、分布式事务等问题。
2.6. 组合模式:处理树形数据(不常用)
“组合模式”,主要是用来处理树形结构数据。正因为其应用场景的特殊性,数据必须能表示成树形结构,这种模式在实际的项目开发中并不那么常用。
2.7. 享元模式 - 共享对象节约内存(和spark的广播变量类似的思路?)
当一个系统中存在大量重复对象的时候,我们就可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用,这样可以减少内存中对象的数量,以起到节省内存的目的。
实际上,不仅仅相同对象可以设计成享元,对于相似对象,我们也可以将这些对象中相同的部分(字段),提取出来设计成享元,让这些大量相似对象引用这些享元。
3. 行为型模式:解决类或对象之间的交互
创建型设计模式主要解决“对象的创建”问题,结构型设计模式主要解决“类或对象的组合”问题,行为型设计模式主要解决的就是“类或对象之间的交互”问题。
行为型模式比较多,有 11 种,它们分别是:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。
3.1. 观察者:(常用)ing
观察者模式将观察者和被观察者代码解耦。观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如,邮件订阅、RSS Feeds,本质上都是观察者模式。
3.2. 模板模式:提供模版逻辑,解耦步骤的实现
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。如下图:
模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
模板模式有两大作用:复用和扩展。
- 复用指的是,所有的子类可以复用父类中提供的模板方法的代码。
- 扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
除此之外,这里还涉及到回调。它跟模板模式具有相同的作用:代码复用和扩展。在一些框架、类库、组件等的设计中经常会用到,比如 JdbcTemplate 就是用了回调。
3.3. 策略模式:避免冗余的if-else
策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。策略的创建由工厂类来完成,封装策略创建的细节。策略模式包含一组策略可选,客户端代码选择使用哪个策略,有两种确定方法:编译时静态确定和运行时动态确定。其中,“运行时动态确定”才是策略模式最典型的应用场景。
在实际的项目开发中,策略模式也比较常用。最常见的应用场景是,利用它来避免冗长的 if-else 或 switch 分支判断。它也可以像模板模式那样,提供框架的扩展点等等。
【设计模式之美】策略模式实践:不同大小(采用不同的策略)文件进行排序
3.4. 职责链模式:顺序执行链条
在职责链模式中,多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。
3.5. 迭代器模式:遍历对象
迭代器模式也叫游标模式,它用来遍历集合对象。大部分编程语言都提供了现成的迭代器可以使用,我们不需要从零开始开发。
遍历集合一般有三种方式:for 循环、foreach 循环、迭代器遍历。后两种本质上属于一种,都可以看作迭代器遍历。相对于 for 循环遍历,利用迭代器来遍历有 3 个优势:
- 迭代器模式封装集合内部的复杂数据结构,直接使用容器提供的迭代器即可;
- 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一;
- 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。
3.6. 状态模式:事件触发比较复杂时
状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。状态机又叫有限状态机,它由 3 个部分组成:状态、事件、动作。其中,事件也称为转移条件。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。
针对状态机,我们总结了三种实现方式。
- 第一种实现方式叫分支逻辑法。利用 if-else 或者 switch-case 分支逻辑,参照状态转移图,将每一个状态转移原模原样地直译成代码。对于简单的状态机来说,这种实现方式最简单、最直接,是首选。
- 第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。
- 第三种实现方式就是利用状态模式。对于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种实现方式。
3.7. 访问者模式:不常用
访问者模式允许一个或者多个操作应用到一组对象上,设计意图是解耦操作和对象本身,保持类职责单一、满足开闭原则以及应对代码的复杂性。
如果你的同事不了解这种设计模式,可能就会读不懂、维护不了你写的代码。所以,除非不得已,不要使用这种模式。
3.8. 备忘录模式:备份与恢复
备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
备忘录模式的应用场景也比较明确和有限,主要用来防丢失、撤销、恢复等。它跟平时我们常说的“备份”很相似。两者的主要区别在于,备忘录模式更侧重于代码的设计和实现,备份更侧重架构设计或产品设计。
对于大对象的备份来说,备份占用的存储空间会比较大,备份和恢复的耗时会比较长。针对这个问题,不同的业务场景有不同的处理方式。比如,只备份必要的恢复信息,结合最新的数据来恢复;再比如,全量备份和增量备份相结合,低频全量备份,高频增量备份,两者结合来做恢复。
3.9. 命令模式:不常用
命令模式在平时工作中并不常用,你稍微了解一下就可以。
3.10. 解释器模式:一组解释单元组成的解释器
解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。
3.11. 中介模式:简化对象交互关系
中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系。
观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。
- 在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。
- 而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。
三. 设计模式进阶
-
深入学习进阶设计模式
并发模式,如生产者-消费者模式、读写锁模式。 -
学习反模式及其避免
理解反模式(如过度设计、不足设计等)及其对软件设计的影响。 -
领域驱动设计(DDD)与设计模式的结合
理解如何将领域驱动设计中的战略设计与设计模式结合,优化复杂系统的设计。
四. 项目实战
【设计模式-2】策略模式 - 避免冗余的if-else判断。数据库迁移框架、flink 类型转换框架例子对策略模式的使用