导图社区 设计模式思维导图
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。
编辑于2021-06-29 18:52:07设计模式
创建型模式
单例模式singleton
一个类只允许创建一个对象,那么这个类就是单例类。在业务上有些数据在系统中只适合保存一份,就比较适合设计成单例类
单例模式的唯一性
进程间唯一
线程间唯一
通过获取线程id实现
在golang中无法使用,因为golang中使用的协程没有暴露协程id
集群间唯一
通过外部共享存储的锁进行,例如文件
实现方式
饿汉式
类加载的时候实例就已经创建好了
实例创建过程是线程安全的
不支持延时加载
懒汉式
用到的时候再去创建
创建过程需要加锁
支持延时加载,不支持高并发
双重检测
在懒汉式的基础上将方法的锁改成类级别的锁
相对于懒汉式锁的粒度更小,不用每次都去获取锁
静态内部类(JAVA)
枚举(JAVA)
建造者模式builder
应用场景
类中的属性比较多
类的属性之间有一定依赖关系,或者是约束条件
存在必选和非必选的属性
希望创建不可变的对象
作用
封装复杂对象的创建过程,使对象使用者不感知复杂的创建逻辑
可以一步步按照顺序对成员进行赋值,或者创建嵌套对象,并最终完成目标对象的创建
对多个对象复用同样的对象创建逻辑
和工厂模式的区别
工厂模式
用于创建类型相关的不同对象
创建者模式
用于创建参数复杂的对象
工厂模式factory
简单工厂
实现简单,有较多的ifelse分支,适用于改动不频繁的方法
工厂方法
当对象的创建逻辑较为复杂,不只是简单的new一下就可以,而是要组合其它类对象,做各种初始化操作的时候,采用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个类都不至于过于复杂。然后使用一个简单工厂来创建工厂方法即可
抽象工厂
使用的场景比较少。可以让一个工厂负责创建多个不同类型的对象,有效减少工厂类的个数
原型模式prototype
结构型模式
代理模式proxy
在不改变原始类的情况下,通过引入代理类来给原始类附加功能
应用场景
RPC
缓存
静态代理
代理类实现和原始类相同的接口,每个类都单独编辑一个代理类
一方面需要在代理类中将原始类的所有方法全部实现一遍,并且为每个方法都附加相似的代码逻辑;另一方面如果要添加代理功能的类不止一个,需要针对每个类都创建一个代理类
动态代理
不事先为每个类创建代理类,而是在运行的时候动态创建代理类,然后在系统中用代理类替换掉原始类。一般采用反射实现
桥接模式bridge
桥接模式主要用于将抽象部分和实现部分进行解耦,使得它们能够各自往独立的方向变化。它解决了在模块有多种变化方向的情况下,用继承所导致的类爆炸问题。
例子
一个产品有形状和颜色两个特征(变化方向),其中形状分为方形和圆形,颜色分为红色和蓝色。如果采用继承的设计方案,那么就需要新增4个产品子类:方形红色、圆形红色、方形蓝色、圆形红色。如果形状总共有m种变化,颜色有n种变化,那么就需要新增m*n个产品子类!现在我们使用桥接模式进行优化,将形状和颜色分别设计为一个抽象接口独立出来,这样需要新增2个形状子类:方形和圆形,以及2个颜色子类:红色和蓝色。同样,如果形状总共有m种变化,颜色有n种变化,总共只需要新增m+n个子类!
许多设计模式都是将庞大的类拆分成较小的类,然后通过某种更合理的结构组装在一起
装饰器模式decorator
主要解决继承关系过于复杂的问题,通过组合来代替继承。主要作用是给原始类新增功能
实现方式
继承
接口
和代理模式的区别
代码形式上几乎没有什么差别
代理模式:主要是给原始类添加访问控制
装饰器模式:主要是给原始类添加新功能
适配器模式adapter
将不兼容的接口转化为兼容的接口
实现方式
类适配器
继承实现
对象适配器
组合实现
如何选择
需要适配的接口多不多
多
是否需要大量修改
需要
对象适配器
不需要
类适配器
不多
二者皆可
应用场景
封装有缺陷的接口设计
统一多个类的接口设计
替换依赖的外部系统
兼容老版本接口
适配不同格式的数据
区别
代理模式是一种事后的补救策略。适配器提供跟原始类不同的接口。而代理模式、装饰模式提供的都是和原始类相同的接口
外观模式facade
主要是为子系统提供了一个更高层次的对外统一接口
应用场景
解决易用性问题
用来封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层次的接口
解决性能问题
减少网络请求
解决分布式事务问题
不用多次调用,一次调用就可以在同一个进程的事务中解决
组合模式composite
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性
应用场景
业务对象必须能够表示成树形结构
享元模式flyweight
享元模式是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象
当我们决定对一个重型对象采用享元模式进行优化时,首先需要将该重型对象的属性划分为两类,能够共享的和不能共享的。前者我们称为内部状态(intrinsic state),存储在享元中,不随享元所处上下文的变化而变化;后者称为外部状态(extrinsic state),它的值取决于享元所处的上下文,因此不能共享。比如,文章A和文章B都引用了图片A,由于文章A和文章B的文字内容是不一样的,因此文字就是外部状态,不能共享;但是它们所引用的图片A是一样的,属于内部状态,因此可以将图片A设计为一个享元
通常与工厂模式成对出现
行为型模式
观察者模式observer
观察者模式是一种行为设计模式, 允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。也叫发布-订阅模式
实现方式
同步阻塞
异步非阻塞
使用协程实现
同进程
跨进程
使用消息队列
模板模式template
模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤
模板方法模式建议将算法分解为一系列步骤, 然后将这些步骤改写为方法, 最后在 “模板方法” 中依次调用这些方法。
应用场景
扩展
框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制框架的功能
复用
所有的子类可以复用父类中提供的模板方法的代码
与回调的区别
同步回调
回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象间的关系
模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系
异步回调
更类似于观察者模式
策略模式strategy
定义一簇算法类,将每个算法分别封装起来,让它们可以相互替换,策略模式可以使算法的变化独立于使用它们的客户端
解耦
工厂模式
解耦对象的创建和使用
观察者模式
解耦观察者和被观察者
策略模式
解耦的是策略的定义、创建、使用这3部分
实现
包含一个策略接口和一组实现这个接口的策略类
创建
策略模式会包含一组策略,在使用时一般通过类型来判断使用的是哪个策略
创建的时候可以采用工厂模式来创建
使用
客户端在使用的时候定义,或者通过配置文件的方式动态指定
责任链模式chain of responsibility
为某个请求创建一个对象链,每个对象依次检查此请求,并对其进行处理,或者将它传给链中的下一个对象
责任链中每个对象都拥有同一个父类(或接口)
应用场景
消息过滤器、权限拦截器
用户发帖内容进行广告过滤,涉黄过滤,敏感词过滤等
状态模式state
状态模式将事件触发时的状态转移和动作执行拆分到不同的状态类中,以避免条件分支判断
常用于实现状态机
状态机常用于游戏、工作流等
有限状态机FSM
组成
状态
事件
事件也称转移事件
事件触发状态的转移和动作的执行
动作
实现方式
分支逻辑法
当状态和逻辑比较少的时候可以使用,否则会有大量if else
查表法
对于状态较多,状态转移比较复杂的状态机来说,查表法比较合适,如游戏
状态模式
对于状态不多,状态转移比较简单,但事件触发执行的动作包含的业务逻辑比较复杂的状态机来说首选状态模式,如电商、外卖等
迭代器模式iterator
用于遍历对象,许多语言已内置这种模式,但go没有
实现
容器
容器通过iterator等方法创建迭代器
容器迭代器
迭代器中需要定义hasNext、currentItem、next等方法
待遍历的容器对象通过依赖注入传递到容器类中
优点
封装了集合内部复杂的数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可
迭代器模式将集合对象的遍历从集合类中拆分出来,放到迭代器类中,让2者的职责更加单一
添加新的遍历方法较容易,更符合开闭原则
因为迭代器都实现自相同的接口,在开发中替换迭代器也更加容易
访问者模式visitor
将算法与其所作用的对象隔离开来,允许一个或多个操作应用到一组对象上,解耦操作和对象本身
备忘录模式memento
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态
应用场景
备份
撤销、恢复
实现
原发器
原发器类可以生成自身状态的快照,也可以在需要时通过快照恢复自身
备忘录
备忘录是原发器快照的值对象,通常做法是将备忘录设置成不可变的,并通过构造函数一次性传递数据
负责人
负责人通过保存备忘录栈来记录原发器的历史状态。当原发器需要回溯历史状态时,负责人将从栈中获取最顶部的备忘录,并将其传递给原发器的恢复方法
命令模式command
命令模式是将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作
实现
主要是把函数封装为对象,实现相同的接口,使其可以通过参数传递
在go中函数可以直接当做参数传递,并且可以命名函数为一个类型,所以不需要使用对象来实现
应用场景
通过操作来参数化对象
将操作放入队列中
操作回滚
与策略模式的区别
设计意图
命令模式
不同的命令中包含了不同的操作和功能,不能够相互替换
策略模式
不同的策略是为了达到相同目的的不同实现,它们之间可以相互替换
实现
二者类似
都是实现了相同的接口
命令模式通过不同的事件执行不同的命令
策略模式通过不同的配置或参数使用不同的算法
解释器模式interpreter
解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法
实现关键
将语法的解析拆分到各个小类中,以此来避免大而全的解析类
将语法规则拆分成一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析
优点
可扩展性高、灵活
易于实现简单的文法
缺点
可使用场景少
对于复杂的文法较难维护
会引起类膨胀
采用的是递归调用的方法
中介者模式mediator
中介模式定义了一个单独的中介对象来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互
与观察者模式的区别
中介模式
只有当参与者之间的交互关系错综复杂,维护成本很高的时候,我们才考虑使用中介模式
观察者模式
大部分情况下,交互关系往往都是单向的,一个参与者要么是观察者,要么是被观察者,不会兼具2种身份