笔者自学设计模式的内容源自大佬网站,以下所有内容均在此基础上加入了个人理解
1 什么是设计模式
设计模式是解决复杂业务逻辑的方案和工具, 是代码可读性的重要保证
其实,在日常的社畜生活中,我已经频繁使用过不下一种的设计模式来解决爸爸们提出的各种在他们世界里合理的需求,最直观的讲,这大概就是hr口中所谓的“三年到五年经验”。
相信很多人在开发中都有过这种感觉:这种情况我遇到过,要这么建实体类,要这么处理,才能防止以后突然改需求的时候不需要重写全部代码。没错,在我眼里,使用设计模式设计的代码,是可以在无法阻止的需求变更发生时,极大的减轻开发人员工作量的东西。
我只是一个普通的小程序员,所以不想用什么理论性的语言给设计模式下定义,它只是我提高工作效率,能有更多时间划水摸鱼陪女朋友的工具之一。目前看来,这个工具是最厉害的。
用几个特点来代表设计模式:
- 解决问题的方案
- 优化逻辑的思路
- 不局限于语言
- 事半功倍
最后要注意一件事,设计模式只是我们用来解决复杂业务逻辑的方案和工具,如果项目中总共只有三四个类,不到一百行代码,完全没有使用的必要性,切莫矫枉过正。
2 为什么要学设计模式
我觉得很多程序员都会和我一样,经历这样一段心路历程:
- 学会了新的语言、技术
- 迫不及待想要写点东西试试
- 编码、踩坑
- 终于实现了一个自认为很棒的例子或功能,我要上传github,我要成为大佬
- 一个星期后荒废
代码能带给我们许多正反馈,同时也容易让我们变得急躁和迫切。
在我看来,一个优秀的程序员的时间分配应该是:读优秀代码>想代码逻辑>编码,让我们进步的是开源与知识,不是造轮子。
所以,能读懂别人的设计、能多思考自己的设计,才是编码能力进步的关建,而这一切,都基于对多种设计模式的了解与认知,站在GoF的肩膀上,我们才能通过代码去影响别人的项目、自己的项目和全世界
所以,我们要学习设计模式,了解设计模式,组合使用各种不同的设计模式。
3 设计模式分类
根据我学习的网站,将设计模式分为以下三种大类:
- 创建型模式
- 结构型模式
- 行为模式
该网站对每种模式的介绍十分清晰,但在什么情况下选择什么模式的讲解,个人认为比较晦涩,所以我想借助自己的理解与经验,对其进行简单的整理,方便之后的使用。
3.1 创建型模式
创建型模式主要针对的是系统内对象的创建,让我们的开发脱离最简单的new,上升一个档次
| 模式 | 适用范围 | 举例 |
|---|---|---|
| 工厂模式 | 在返回给客户端的结果中,数据拥有两种以上的类别,但客户端并不关心 | 以普通士兵和高级士兵为例,二者的都可以进行移动、作战、驻扎等行为,但两者的战斗力、机动力等属性不同,所以我们创建士兵工厂、普通士兵工厂和高级士兵工厂 |
| 抽象工厂模式 | 在返回给客户端的结果中,数据拥有两种以上的系列,但客户端并不关心 | 以战役中有红方蓝方为例,二者都拥有普通士兵和高级士兵,除了普通士兵与高级士兵存在工厂模式中的差异外,红方普通士兵和蓝方普通士兵也存在差异,所以我们创建士兵工厂、红方士兵工厂和蓝方士兵工厂 |
| 生成器模式 | 在返回给客户端的结果中,数据拥有两种以上的类别且生产过程完全相同,但客户端并不关心 | 以战役中有普通士兵和高级士兵为例,二者在生产过程中,都需要配备相同的机枪,但由于高级士兵的使用技术熟练,其配备机枪后的作战能力要高于普通士兵,所以我们创建生产工厂、普通士兵生成器和高级士兵生成器,在工厂生产不同士兵时,选择不同的生成器 |
| 原型模式 | 在返回给客户端的结果中,需要大量属性完全相同的多个不同对象 | 原型模式个人认为主要应用于缓存,以普通士兵为例,假设其生产过程固定且较为耗时,但克隆相对迅速,当系统需要生产十个普通士兵时,我们可以先生产第一个将其缓存起来,剩余九个通过克隆的形式完成,所以我们实现普通士兵的克隆方法即可 |
| 单例模式 | 在多次返回给客户端的结果中,只需要一个相同的产品 | 单例模式个人认为主要应用于池化概念(如数据库连接池),并且可以和上面的四种模式结合。以抽象工厂模式举例,在系统中只需要生成一次士兵工厂、红方士兵工厂和蓝方士兵工厂并将它们放入工厂池中,在每次进行生产时,我们直接获取需要的工厂进行创建即可 |
最后提出需要注意的两点内容:
- 抽象工厂模式生产的是一系列产品,如果是单个产品,请使用工厂模式
- 生成器模式是用相同的生产流程产生不同的产品,与此同时的进阶,是可以创建管理者类,构建不同的生产流程,切勿本末倒置。
3.2 结构型模式
如果说创新型模式是系统内对象的创建,那结构型模式便是将创建好的对象进行组合,创建出灵敏高效的代码结构
| 模式 | 适用范围 | 举例 |
|---|---|---|
| 适配器模式 | 将一个不想或不能修改的对象伪装成客户端需要的对象 | 以士兵汇报坐标为例,在战场中,士兵可能携带多种不同坐标系的定位装置,在向指挥系统提交位置时,地图系统只接受卫星坐标。为了能给指挥官准确定位,我们需要在定位装置和地图系统中间创建适配器,将不同的坐标系转换为卫星坐标 |
| 桥接模式 | 将两种类型的具体对象交互转换为接口级别的交互 | 以士兵使用机枪射击为例,士兵可以接收开火命令,机枪可以接收发射子弹命令。士兵分为普通与高级,机枪也分不同的种类,但指挥官并不关心。所以,我们创建一个通用士兵和通用机枪,并在其基础上衍生出不同的士兵和不同的机枪,而指挥官只需要下达开火命令,就可以完成“高级士兵使用加速机枪发射子弹”这一具体情况 |
| 组合模式 | 构建同一类型的树状结构 | 组合模式在本人看来,最主要的功能是在系统中实现对树状结构下达指令与对单个对象下达指令相同,以士兵和队伍为例,指挥官可以下令士兵进行射击,也可以下令队伍进行射击,从指挥官的角度,给队伍下达命令要比给队伍中的每个士兵下达命令轻松且清晰的多 |
| 装饰模式 | 在原有的方法之前或之后增加新的方法 | 如果熟悉python,那么一定对装饰器这个词不陌生,可以去菜鸟课程中详细了解。以士兵使用机枪射击举例,士兵扣动扳机是射击的必须动作不能修改,那如何实现士兵开火且不被敌军发现呢?我们只需要在扣动扳机之前,让士兵为机枪装上消音(装饰)器即可。Java中的AOP严格来说不是装饰模式,但从业务的角度来讲,我认为AOP也可以说是完成了装饰器的功能 |
| 外观模式 | 基于原有的多个不同类型细节方法,创建一个新的业务方法,包含其原有操作 | 以士兵对敌人进行射击为例,从士兵的角度来看,完成这项任务需要装备机枪、装备消音器、较准、瞄准、屏息、扣动扳机,但从指挥官的角度来看,他并不关心士兵是否装备了消音器、是否较准、是否屏息,他需要的是下达命令和反馈结果。所以,我们新建指挥官命令外观,编写攻击命令方法 |
| 享元模式 | 将同一种类的多个对象中重复的属性进行提取,达到节省内存的效果 | 享元模式和单例模式在概念上类似,都是一种数据缓存的方式。以指挥官的地图沙盘为例,演练中我方会有成百上千个士兵、坦克、战车,分别用三种图标进行展示。对于一千个士兵来说,图标都是相同的,所以我们在生成一千个士兵前先生成图标对象,再将士兵的图标属性指向这个对象,便可极大的减少地图沙盘的内存占用,让指挥官操作更为流畅 |
| 代理模式 | 以增强对象功能为目的对其进行包装,且不改变原对象方法的输入输出 | 代理模式与装饰模式极为相似,但从客户端的角度来看却不相同。以士兵使用机枪射击举例,若想要士兵安装消音器,在装饰模式下需要指挥官首先将消音器配发给士兵,对于指挥官来说消音器是可感知的设备,而代理模式是士兵自己研制并安装消音器,对于指挥官来说,他只知道下达命令与结果,无法感知消音器的存在 |
再简单的总结下几种模式的特点与不同:
- 适配器、装饰、外观、代理模式,都是在不改变原有对象/方法的前提下实现的,其中较为特殊的是代理模式,对于客户端来说,代理模式包装后的新对象可以替换原对象,客户端无法感知其变化。
- 享元模式主要用于数据缓存
- 组合模式中各个元素理论上是相同的,是一种树形结构,例如我们最常见的文件夹结构
3.3 行为模式
在创建对象并组织对象结构后,最复杂也是业务逻辑最多的步骤就来了,对象(结构对象)之间的交互。
在结构型模式中,也会涉及到部分对象的交互,但其核心目的是为了组成结构面向客户端,而行为模式则是在接收到客户端指令后,如何快速高效的完成对象之间的交互,即如何让士兵们高效的执行指挥官的命令。
| 模式 | 适用范围 | 举例 |
|---|---|---|
| 迭代器模式 | 将复杂对象集合的遍历方法进行提炼总结,节省代码并隐藏内部逻辑 | 以为队伍配发装备为例,假设需要在今天明天分两次为队伍配发不同装备,队伍内拥有成百上千个士兵,如何保证高效有序,且每个士兵都可以获得装备呢?因此,我们创建队伍士兵迭代器,队伍内的士兵按照编制、岗位、年龄等进行一系列复杂排序,但这种排序方式指挥官并不知情,他只需要不停的喊“下一个”并配发装备即可 |
| 责任链模式 | 将一系列命令组合成一个有序序列依次执行,且各个命令之间相互独立 | 以指挥官登录地图沙盘系统为例,假设登录此系统需要账号、密码和指挥官编号。指挥官在完成信息输入后,系统先校验账号密码是否存在且正确,之后在部队中搜索指挥官编号是否存在。这两项操作完全独立,但如果账号密码不正确,则不需要进行指挥官编号的搜索,所以我们创建登录校验责任链,第一个任务为校验账号密码,第二个任务为校验指挥官编号是否存在 |
| 命令模式 | 一个对象需要用同一方法执行得到不同结果 | 以士兵攻击敌军为例,假设每个士兵只能配备一种武器,士兵对敌人发起进攻可以使用匕首、手枪、机枪、火箭炮、薅头发等多种方式。对于士兵来说,使用武器攻击敌人是常识,区别在于自身装备了什么武器。所以,我们创建武器对象,并为各种不同的武器编写攻击敌人方法,指挥官只需要在战役开始前为士兵装备对应的武器,就可以进行不同形式的作战战况 |
| 访问者模式 | 不同对象需要用同一方法执行得到不同结果 | 访问者模式是命令模式的加强版。 以士兵、坦克、飞机攻击敌军为例,士兵使用机枪,坦克使用大炮,飞机使用导弹(如同命令模式中的三种不同装备),但士兵的攻击是扣动扳机,坦克是按下开炮按钮,飞机是按下轰炸按钮,三者的攻击操作并不相同。所以,我们创造一个攻击敌军访问者,里面包含了访问士兵、坦克、飞机的三个方法和一个攻击方法,同时让士兵、坦克和飞机都可以接收它的访问(即共同实现访问接口)。对于指挥官来说,他只需要让访问者访问三者,并下达攻击指令即可 |
| 备忘录模式 | 一个对象需要记录历史版本 | 备忘录模式我认为是最容易理解的模式之一,它的存在就是为了记忆对象的历史记录,例如文本编辑器、浏览器等,但备忘录模式的应用却又是最难的,它往往不会单独使用,会结合命令模式使用。以文本编辑器撤销操作为例,客户端每执行一个命令,编辑器会记录未执行前的文本,此时执行撤销,编辑器会使用记录的最新版本替换当前的文字内容。(拓展思路,撤销和恢复如何用备忘录模式实现呢?在复杂的插销与恢复中可以学习) |
| 中介者模式 | 把多个相互关联的对象解耦,通过一个对象完成它们之间的交互 | 如果了解vue的父子组件交互,那对中介者模式的理解会相对轻松。以指挥官对士兵下达作战命令为例,假设指挥官需要下达进攻、防守、侦察三个命令给三个士兵,传统模式下,他需要先选择一个士兵去完成进攻命令,再对这个士兵指示要进攻哪个阵地,其他命令同理。所以,我们创建指挥中介者,指挥官选择指挥员身份登录,一号士兵选择攻击士兵身份登录,以此类推。此时,指挥官只需要在指挥中介者中点击攻击按钮,一号士兵便可直接执行命令 |
| 观察者模式 | 多个对象通过对事件的订阅、触发和响应完成交互 | 观察者模式和中介者模式非常相似,中介者模式中的举例也适用于观察者模式,下面的例子主要用于区分中介者模式和观察者模式。 以两个队伍相互配合阻截敌人为例,两个队伍均使用指挥中介者平台,一队订阅二队的位置信息,二队同样也订阅一队的位置信息,所以,在指挥中介者中,并不存在指挥官这个角色,或者说一队二队即是指挥官角色也是士兵角色,在这种情况下,我们可以理解为是观察者模式。 |
| 状态模式 | 一个对象拥有三种以上的状态和操作,且不同状态下的操作实现不一致 | 大名鼎鼎的有限状态机就是状态模式,等我弄懂了再回来讲它,用个人理解的最通俗的话来讲,状态模式就是用来替换大块恶心的if else和switch的。 以士兵的状态和行动为例,假设士兵拥有待命、移动、驻扎三种状态,指挥官可以对其下达前进、开火、撤退三种命令,平常的做法是在接收到三个命令后,判断士兵的状态,之后再执行对应的操作。例如接收到前进命令,待命状态的士兵会直接进行移动,而驻扎状态的士兵先要取消驻扎带来的防御力提升加成,再进行移动。我们转换下思路,先赋予士兵状态属性,并创建三个不同的状态,之后在其中分别实现接收到三种不同命令后的行动方法。这样,无论在什么情况下,接收到什么命令,我们只需要获取士兵当前的状态,执行状态中对应的方法即可 |
| 策略模式 | 在一个业务逻辑中,两种以上的方法需要两种以上的实现 | 单独看适用范围,策略模式似乎只是把状态模式中的三种改为两种,从代码实现上看也似乎相同。但二者还是有区别的,策略模式下的各种方法实现无法相互感知,而状态模式下的方法中,是可以将状态进行变更的。 以士兵对指定敌人发起进攻为例,假设士兵需要获取敌军位置,选择进攻武器。所以,指挥官创建策略接口,并设计了两种不同的策略,一种是通过目视获取地方位置,再使用手枪快速进行突进,一种是通过雷达获取敌军位置,再使用火炮进行远程轰炸。无论是哪种策略,在整个行动中,士兵都是执行了相同的行为,即获取敌军位置和选择进攻武器。 若是指挥官后续有新的策略,则只需要将其设计好,并在士兵行动前进行指定即可 |
| 模板方法模式 | 在一个业务逻辑中,需要重写部分方法 | 依旧看适用范围,模板方法模式仿佛是状态模式和策略模式的最简化版,它和二者的区别主要在于实现方式,模板方法模式的实现是基于类的继承的,一旦确定则不可切换。 我们用策略模式中的例子进行比较,在策略模式中,指挥官可以在士兵通过目视获取地方位置后,临时修改策略,采用火炮进行远程轰炸,但这种临时修改策略的行为在模板方法模式中是不可行的,一旦选择了进攻策略便无法动态修改。 其实,工厂模式也是一种特殊的模板方法模式,但二者解决问题的目的不同,工厂模式是为了创建,模板方法模式是业务逻辑的处理。 |
在上表中,其实我对10中行为模式又进行了简单的排序分类:
- 结构行为:迭代器模式,较为特殊,重点在迭代复杂业务对象的结构,可以和后面的各种模式进行结合
- 命令行为:责任链模式/命令模式/访问者模式/备忘录模式,四者均属于执行客户端命令的模式,重点在对象如何完成客户端分配的任务
- 交互行为:中介者模式/观察者模式,两者的重点在于对象之间的交互
- 系统行为:状态模式/策略模式/模板方法模式,三者虽然也是对客户端命令的处理,但个人认为其使用范围更偏向于系统,而非对象本身
再简单的总结下几种模式的不同:
- 命令模式针对的是一种对象,而访问者模式针对的是不同对象
- 中介者模式理论上必然存在一个发送请求对象和一个接收请求对象,而观察者模式理论上可以所有对象既是接收请求对象又是发送请求对象,不存在中心
- 状态模式的方法中,可以相互感知甚至修改对象的状态属性,而策略模式的方法是相互独立的,无法感知其他方法的存在。
- 模板方法模式的业务逻辑一旦选择无法临时切换,而策略模式可以在进行中进行动态切换。
4 小结
以上内容只是本人在学习设计模式后的个人理解,目的是为了简单粗暴的总结出,在实际开发过程中遇到不同情况时,如何快速选择对应的设计模式
表格中所有的适用范围和量化标准均是个人定义,如何选择设计模式还是要看具体的业务逻辑,代码胜于一切空谈
如果将来有时间,可能会将表格中的举例转换为实际的Java/Python代码
强烈推荐将我学习设计模式的大佬网站和本篇内容结合起来使用。