导图社区 设计模式
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。这里整理了经典23种设计模式使用场景以及代码模板,设计原则与思想。
编辑于2021-04-07 10:29:09设计模式
概念
使脑图码配合github代码
https://github.com/hehan-wang/design-pattern/
设计模式要干的事情就是解耦。 创建型模式是将创建和使用代码解耦, 结构型模式是将不同功能代码解耦, 行为型模式是将不同的行为代码解耦
面向对象
封装 Encapsulation
对外隐藏实现细节
抽象 Abstraction
抽象屏蔽使用者差异 使用者关注功能而不用关心实现
继承 Inheritance
继承使代码复用
多态 Polymorphism
子类可以替换父类 使用继承或者duck-typing(python go)
代码标准
1. 可维护性 Maintainability
团队觉得很难维护的时候既很差 很难量化 具体分析
2. 可读性 Readability
任何傻瓜都会编写计算机能理解的代码。好的程序员能够编写人能够理解的代码
code review 能监测代码可读性
3. 可扩展性 Extensibility
增加新功能 不改或者少改代码 由于需求总是会变 扩展性好的代码能使开发成本降低 还是比较重要的
4. 灵活性 Flexiblity
易扩展 易复用 易用
5. 简洁性 Simplicity
清晰 简单
思从深而行从简 高手都是用最简单的方式解决最复杂的问题
KISS原则
6. 可复用性 Reusablity
拒绝重复代码
继承 多态
DRY原则
7. 可测试性 Testability
易于写单元测试的代码一般是模块化较好的代码
编码规范可以写出可读性好的代码 设计模式可以提高扩展性等
思考
实际上,设计模式之间的主要区别还是在于设计意图,也就是应用场景。单纯地看设计思路或者代码实现,有些模式确实很相似,比如策略模式和工厂模式。
参考
1. https://time.geekbang.org/column/intro/250?utm_source=baidu-ad&utm_medium=ppzq-pc&utm_campaign=guanwang&utm_content=title&utm_term=baidu-ad-ppzq-title
2. https://www.runoob.com/design-pattern/design-pattern-tutorial.html
设计原则与思想
SOLID
单一职责原则 SRP(Single Responsibility Principle)
一个类或者模块只负责一个职责
作用:SRP为了实现代码高内聚、低耦合 提高代码复用性、可读性、可维护性
开闭原则 OCP(Open Close Principle)
对扩展开放 对修改关闭
不是完全避免修改 而是以最小的代价修改
符合开闭原则:装饰、策略、模板、职责链、状态
作用:提高代码的扩展性
里氏替换原则 LSP(Liskov Substitution Principle)
子对象能够替换父对象出现的任何地方
核心是“design by contract 按照协议编程” 子类完整按照父类定的协议实现 并做一定加强
作用:保证协议语义的正确性
接口隔离原则 ISP(Interface Segregation Principle)
使用者不应该强制依赖不需要的接口
不应该implement不必要的Interface
不应该依赖不必要的Object
函数的功能要单一
和单一职责原则的区别
SRP 从服务端的设计看
ISP 从客户端的调用接口是否依赖不需要的功能看
作用:减少歧义和误操作
依赖反转原则 DIP(Dependency Inversion Principe)
高层模块不应该依赖底层模块 抽象不应该依赖实现
作用:提高拓展性 能够更好的基于接口编程
KISS (Keep it simple and stupid)
用最简单的实现方式
不要重复造轮子(使用现有工具类)
不要过度设计
如何做(保持简单)
YGNI (You ain’t gonna need it)
你不会需要它
不要过度设计
要不要做
DRY (Don't repeat youself)
代码执行重复
同样的逻辑执行了多次 效率也很低
实现逻辑重复
同样的功能实现了两遍
功能语义重复
重复代码
LOD (Law of Demeter )
迪米特法则 最小知识原则 每个模块只应该了解与它关系密切的模块
高内聚 松耦合
作用:提升代码可读性 可维护性
高内聚
指导类本身设计
指相同的功能放入同一个类
松耦合
指导类之间的依赖关系
类之间的依赖关系少 清晰的依赖关系 可以减少一个类修改带来的影响
依赖迪米特法则
规范和重构
重构
概念
难点:重构需要你能洞察出代码存在的坏味道或者设计上的不足,并且能合理、熟练地利用设计思想、原则、模式、编程规范等理论知识解决这些问题
定义:重构是一种对软件内部的改善,在不改变原有对外可见行为基础上,使其更容易理解 修改成本更低 不改变功能的情况加增加代码质量
重构+单测 防止增加bug
why
持续重构很有必要。防止代码腐坏到无可救药
好的代码都是持续重构出来的。不是一蹴而就的
重构对工程师成长也有重要意义
what
大型重构
分层、模块化、解耦、抽象可复用组件等等
小型重构
规范命名、规范注释、消除超大类或函数、提取重复代码等等
when
持续重构 当出现坏味道 或者重复代码需要提取的时候
how
idea refactor功能很好实现了重构
通过代码分析工具 发现代码问题
团队提高代码质量风气 防止"破窗效应"
规范
每个团队不一样 但是团队统一就好。可以参考阿里巴巴开发手册 或者谷歌开发手册
https://time.geekbang.org/column/article/188622?utm_source=baidu-ad&utm_medium=ppzq-pc&utm_campaign=guanwang&utm_content=title&utm_term=baidu-ad-ppzq-title
创建型 Creational
如何创建对象
单例模式 Singleton
what 意图
保证一个类只有一个实例
when 使用场景
处理资源访问冲突 1.多个对象访问同一资源(写文件等)
全局唯一类 1.序列号生成器 2.需要实例化的工具类
Java Calendar
how 如何解决
关闭创建对象接口 提供唯一获取实例入口
饿汉式 懒汉式 双重检测 静态内部类 枚举 推荐静态内部类法
饿汉式
优缺点
优点
1. 减少内存开销
2. 避免资源访问冲突
缺点
1. 对OOP支持不友好
2. 隐藏类之间的依赖关系
3. 对扩展性不好
4. 测试性不好
5. 不支持有参构造
工厂 Factory
what 意图
创建一组复杂的相同"接口"的对象
when 使用场景
Spring DI容器
根据不同类型创建不同对象 (根据类型创建不同的配置解析器)
根据配置动态的创建不同对象
how 如何解决
简单工厂: 工厂方法: 使用多态代替简单工厂if else 抽象工厂:工厂的工厂 创建多个类型相关的对象(spring ioc容器和FactorBean的关系)
优缺点
优点
1. 封装变化 创建逻辑发生变化 对使用者无影响
2. 代码复用 创建代码独立到工厂中可复用
3. 提高扩展性 增加一个实现只需要在工厂增加新的创建类
缺点
1. 复杂性增加
建造者 Builder
what 意图
分步构建复杂对象
when 使用场景
肯德基 汉堡薯条套餐不固定
可以在最后build统一进行有互相依赖的参数校验
Java StringBuilder
Java Calendar
how 如何解决
lombok @Builder
优缺点
优点
1. 易扩展
2. 易用
缺点
1. 代码膨胀
和工厂模式区别 工厂模式创建相关联的不同对象 构建者模式创建相同类型的不同属性的对象
原型模式 Prototype
what 意图
通过已有对象拷贝创建新对象
when 使用场景
创建对象性能开销大的情况(io,数据库等)
how 如何解决
Java clone()
深拷贝和浅拷贝
优缺点
优点
1. 性能高
缺点
1. 浅拷贝容易出现共享对象修改导致全局改变问题
结构型 Structural
对象之间的关系
代理 Proxy
what 意图
在不改变原始类代码的情况下 通过引入代理类给原始类附加功能
when 使用场景
RPC客户端调用(Fiegn Dubbo)
统一日志打印
事务
异常处理
缓存@Cachable
how 如何解决
JDk Proxy 基于接口
Cglib 基于继承
优缺点
优点
1. 修改切面不需要修改原先代码 扩展性高
2. 可维护性高
缺点
1. 复杂度增加
装饰器 Decorator
what 意图
解决继承关系过于复杂问题 通过组合代替继承 增强原始类功能
when 使用场景
Java Collections.unmodifiableColletion()
FileInputStream BufferedInputStream
how 如何解决
优缺点
优点
1. 与"组合代替继承"的区别是可以嵌套多个装饰器。 比如对FileInputStream嵌套BufferedInputStream和DataInputStream
缺点
适配器 Adaptor
what 意图
将一个接口转化成需要的另一个接口 使不兼容的的类可以在一起工作
when 使用场景
Java Collections.enumeration()
slf4j 适配log4j logback等
插座转接头
封装有缺陷接口
统一多个类的接口设计
兼容老版本
替换依赖外部系统
适配不同数据格式
how 如何解决
类适配器(继承)
对象适配器(依赖)
优缺点
优点
1. 可以是不适配的接口统一
缺点
桥接 Bridge
what 意图
将抽象和实现解耦 让他们可以独立变化
通过组合解决两个独立变化的相关维度
when 使用场景
JDBC 驱动是桥接模式的经典应用
how 如何解决
sender分 邮件/微信 notification分 紧急/一般 两者独立变化 通过notification组合sender串联
优缺点
优点
1. 解耦抽象和实现
2. 扩展性
缺点
1. 难理解
区别
代理模式:在不改变原始类接口的条件下,为原始类定义一个代理类。 目的是统一增加额外功能,而非为增强原始功能
桥接模式: 将接口部分和实现分离 从而让他们可以独立改变
装饰器模式: 不改变原始类的基础上 对原始功能进行加强 并支持嵌套使用
适配器模式: 是一种事后补救策略,适配器跟原始类不同接口,而代理 装饰器是实现相同接口
门面模式 Facade
what 意图
门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用
提供统一入口 减少使用者差异
when 使用场景
解决易用性问题
解决性能问题
解决分布式事务问题
how 如何解决
提供对外的统一接口
优缺点
优点
1. 减少依赖
2. 提高易用性
缺点
1. 不符合开闭原则 需求变更需要修改代码
组合模式 Composite
what 意图
将一组对象组织(Compose)成树形结构,以表示一种“部分 - 整体”的层次结构。组合让客户端(在很多设计模式书籍中,“客户端”代指代码的使用者。)可以统一单个对象和组合对象的处理逻辑。
when 使用场景
统一file/directory 计算目录大小
计算部门/人 薪资
how 如何解决
优缺点
优点
1. 统一成树形结构 简化代码
缺点
1. 违反依赖倒置原则
享元模式 Flyweight
what 意图
运用共享技术有效地支持大量细粒度的对象。
when 使用场景
String 常量池
Integer(-127~128)缓存
文本编辑器字体格式
how 如何解决
棋子只有固定个数(车马炮) 设计为共享单元
优缺点
优点
1. 减少内存占用
缺点
1. 提高复杂度 提高理解成本(分离共享和非共享对象)
行为型 Behavioral
对象之间的交互
观察者 Observer
what 意图
对象存在一对多关系的时候 修改一个对象通知其他的依赖它的对象
when 使用场景
EventBus
MQ消息队列
how 如何解决
优缺点
优点
1. 建立触发机制
缺点
1. 可能存在循环依赖
模板 Template
what 意图
定义骨架代码 将一些步骤在子类实现
when 使用场景
Spring RedisTemplate等等Template类
InputStream read()方法
AbstractList addAll()
Servlet doGet() doPost()
Junit
how 如何解决
将流程固定好 把变化的部分抽象出来给子类实现(或者传入lambda表达式)
继承
callback
优缺点
优点
1. 封装不变部分 扩展变化部分 增加可扩展性 复用性
缺点
1. 类会增加
策略 Strategy
what 意图
定义一系列算法 一个个封装起来 并且使他们可以相互替换
不但解决if else 过多问题 ,而且解耦策略定义和使用。 使新增加策略只需要修改一小部分代码,符合开闭原则
when 使用场景
通过文件大小选择多种文件排序器
对接不同api 使用不同的处理器解析参数
how 如何解决
策略初始化在Map中(或者通过工厂创建) 使用类型获取具体策略实现
优缺点
优点
1. 增加策略不用修改其他代码 扩展性好
2. 策略可以复用 复用性好
缺点
1. 类膨胀
责任链 Chain Of Responsibility
what 意图
请求发送者和接受者解耦,将多个接收对象连成串,传递请求, 直到链上的某个结项能够处理为止
when 使用场景
Servlet Filter
Spring Interceptor
敏感词过滤
多logger日志打印
how 如何解决
定义过滤器接口,多个实现类和chain类实现接口。 chain中注册多个过滤器
优缺点
优点
1. 符合开闭原则 复用性 拓展性 新增一个过滤器不用修改其他代码
缺点
1. 类膨胀
迭代器 Iterator
what 意图
提供一种方法顺序访问一个聚合对象中的元素,而又无需暴露该对象的内部表示
迭代器模式将遍历从集合中拆出来,放在迭代器类中。使职责更单一

when 使用场景
Java 集合Iterator
复杂数据结构的遍历(树 图)DFSIterator BFSIterator等
how 如何解决
迭代器类组合集合类 实现迭代逻辑
优缺点
优点
1. 增加遍历算法不改变原有类 符合开闭原则
缺点
1. 类膨胀
状态模式 State
what 意图
解决 触发事件事件造成状态和行为改变
when 使用场景
行为随状态改变而改变的场景
游戏中状态机 (超级玛丽)
how 如何解决
优缺点
优点
1. 状态独立变化 清晰
缺点
1. 增加复杂度
状态机实现
分支逻辑 简单状态机
查表法 状态 转移比较复杂
状态模式 状态不多 状态转移简单 但是实现逻辑复杂
访问者模式 Visitor
what 意图
允许一个或多个操作应用到同一组对象上 解耦操作和对象本身
主要将数据结构与数据操作分离
when 使用场景
Sigle Dispatch语言才需要 Double Dispatch语言不需要 Java 多态是运行时确定执行哪个类, 重载却是在编译时确定执行哪个方法。
对同一组对象(txt/pdf)需要做 不同类型操作(extract compress)的时候
多个类对多个操作
how 如何解决
优缺点
优点
1. 每个操作单独出来 提高扩展性
缺点
1. 违反依赖倒置
2. 违反迪米特法则
备忘录 Memento
what 意图
在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
when 使用场景
游戏存档
后退 前进功能
how 如何解决
memento
优缺点
优点
1. 支持回滚
缺点
1. 消耗资源
命令模式 Command
what 意图
命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。
when 使用场景
linux 客户端命令
任何动作需要传递 或者复操作都可以封装成对象
how 如何解决
将命令封装成对象 发送到队列中 网络中等
优缺点
优点
1. 扩展性
缺点
1. 类膨胀
2. 复杂度增加
解释器 Interpreter
what 意图
解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
定义一套语言和解析
when 使用场景
Spring SPEL
SQL
Mybatis 动态sql
规则引擎
how 如何解决
优缺点
优点
1. 扩展性
缺点
1. 使用场景少
2. 类膨胀
中介模式 Mediator
what 意图
中介模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。
when 使用场景
航空管制 所有飞机不互相交互而是和塔台交互
群聊
how 如何解决
优缺点
优点
1. 降低对象间依赖的复杂度 网状结构 编程星状结构
缺点
1. 中介类会非常复杂 庞大