1. 七大原则

设计模式原则名称 简单定义
开闭原则(Open Closed Principle,OCP) 对扩展开放,对修改关闭
单一职责原则(Single Responsibility Principle, SRP) 一个类只负责一个功能领域中的相应职责
里氏代换原则(Liskov Substitution Principle,LSP) 所有引用基类的地方必须能透明地使用其子类的对象
依赖倒转原则(Dependency Inversion Principle,DIP) 依赖于抽象,不能依赖于具体实现
接口隔离原则(Interface Segregation Principle,ISP) 类之间的依赖关系应该建立在最小的接口上
合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP) 尽量使用合成/聚合,而不是通过继承达到复用的目的
迪米特法则(Law of Demeter,LOD) 一个软件实体应当尽可能少的与其他实体发生相互作用

1.1. 单一职责

一个类只负责一个事情,将业务拆分成最小颗粒

1.2. 开闭原则

一个软件实体应当对扩展开放,对修改关闭。软件实体应尽量在不修改原有代码的基础上进行扩展
软件实体包括以下几部分:

  1. 项目或软件产品中按照一定逻辑规划划分的模块
  2. 抽象类
  3. 方法

开闭原则的优势:

  1. 可以使原来的代码依旧可以运行
  2. 提高代码的可复用性
  3. 提高系统的可维护性

如何实现开闭原则:

  1. 抽象约束
  • 通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法;
  • 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;(针对抽象编程)
  • 抽象层尽量保持稳定,一旦确定即不允许修改。
  1. 元数据控制模块行为, 通俗来说就是通过配置文件来操作数据,spring的控制反转就是一个很典型的例子。
  2. 约定优于配置
  3. 封装变化
  • 将相同的变化封装到一个接口或者类中
  • 将不同的变化封装到不同的类或者接口中(单一职责的体现)

1.3. 里氏替换原则

里氏代换原则(Liskov Substitution Principle,LSP)的定义是:所有引用基类的地方必须能透明地使用其子类的对象,也可以简单理解为任何基类可以出现的地方,子类一定可以出现。 子类实现父类的方法。
里氏替换原则约束:

  • 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
  • 子类中可以添加特有方法(父类中不存在),此时则无法在以父类定义的对象中使用该方法,除非在使用的时候强转基类成子类进行调用。
  • 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

所以我们在运用里氏替换原则的时候,尽量把父类设计为抽象类或者接口,让子类继承父类或者实现接口并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。

1.4. 依赖倒转原则

依赖倒转原则(Dependency Inversion Principle,DIP):抽象不应该依赖细节,细节应当依赖抽象。换而言之,要针对接口编程,而不是针对实现编程。

  • 高层次的模块不应依赖低层次的模块,他们都应该依赖于抽象
  • 抽象不应该依赖具体实现,具体实现应该依赖抽象

开闭原则,依赖倒转原则,里氏替换原则同时出现了,可以说`开闭原则是我们要实现的目标,而里氏替换原则是实现手段之一,而同时里氏替换原则又是依赖倒转原则实现的基础,因为加入没有这个理论,依赖倒转原则是不成立的,无法针对抽象编程,要注意这3个原则基本都是同时出现的。

1.5. 接口隔离原则

接口隔离原则的两个定义:

  • 使用多个专门的接口,而不是使用单一的总接口,既客户端不应该依赖他不需要的接口
  • 类与类之间的依赖关系应该建立在最小接口之上

接口的含义:

  • 一个接口代表一个角色,不应该将不同的角色交给一个接口,因为这样可能会形成一个臃肿的大接口
  • 特定语言的接口,表示接口仅仅是提供客户端行为的接口,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独接口,而不要提供大的总接口

通过接口隔离原则,明白都应该只承担一种相对独立的角色,不应将多角色放在一个接口类中

1.6. 合成复用原则

合成复用原则(Composite Reuse Principle, CRP):尽量使用对象组合,而不是继承来达到复用的目的

1.7. 迪米特法则

迪米特法则(Law of Demeter,LOD),有时候也叫做最少知道原则(Least Knowledge Principle,LKP),它的定义是:一个软件实体应当尽可能少地与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类(中间类或者跳转类)来转达。

迪米特法则的规则:

  • Only talk to your immediate friends(只与直接的朋友通讯),一个对象的”朋友”包括他本身(this)、它持有的成员对象、入参对象、它所创建的对象。
  • 尽量少发布public的变量和方法,一旦公开的属性和方法越多,修改的时候影响的范围越大。
  • “是自己的就是自己的”,如果一个方法放在本类中,既不产生新的类间依赖,也不造成负面的影响,那么这方法就应该放在本类中。

迪米特法则的意义:
迪米特法则的核心观念就是类间解耦,也就降低类之间的耦合,只有类处于弱耦合状态,类的复用率才会提高。所谓降低类间耦合,实际上就是尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。但是这样会引发一个问题,有可能产生大量的中间类或者跳转类,导致系统的复杂性提高,可维护性降低。如果一味追求极度解耦,那么最终有可能变成面向字节码编程甚至是面向二进制的0和1编程。

2. 设计模式分类

2.1. 创建型模式

创建型模式提供创建对象机制,能够提升已有代码的灵活性和可复用性

序号 类型 业务场景 实现要点
1 工厂方法 多种类型商品不同接口,统一发奖服务搭建场景 定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建的过程延迟到子类进行
2 抽象工厂 替换Redis双集群升级,代理类抽象场景 提供一个创建一系列相关或相互依赖对象接口,而无需指定它们具体的类
3 建造者 各项装修物料组合套餐选配场景 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
4 原型模式 上机考试多套试卷,每人题目和答案乱序排列场景 用原型示例指定创建对象的种类,并且通过拷贝这些原型创建新对象
5 单例模式 七种单例模式案例,Effective Java 作者推荐枚举单例 保证一个类仅有一个实例,并提供一个访问它的全局访问点

2.2. 结构型模式

结构型模式结束如何将对象和类组装成较大的结构,并同时保持结构的灵活性和高效

序号 类型 业务场景 实现要点
1 适配器 多个MQ消息体中,抽取指定字段值场景 将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
2 桥接 多支付渠道(微信、支付宝)与多支付模式(刷脸、指纹)场景 将抽象部分与实现部分分离,使它们都可以独立变化
3 组合 营销差异化人群发券,决策树引擎搭建场景 将对象组合成树形结构以表示“部分”、“整体”的层次结构。组合模式使得用户对单个对象和组合对象使用具有一致性
4 装饰器 SSO单点登录功能扩展,增加拦截用户访问方法范围场景 动态地给一个对象添加一些额外地职责。就增加功能来说,装饰器模式相比生成子类更灵活
5 外观模式 基于SpringBoot开发门面模式中间件,统一控制接口白名单场景 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
6 享元模式 基于Redis秒杀,提供活动与库存信息查询场景 运用共享技术有效地支持大量细粒度地对象
7 代理模式 MyBatis DAO接口,使用代理类方式操作数据库原理实现场景 为其他对象提供一种代理可以控制这个对象地访问

2.3. 行为模式

序号 类型 业务场景 实现要点
1 责任链 模拟618电商大促期间,项目上线流程多级负责人审批场景 避免请求发送者与接收者耦合在一起,让多个对象都有可能被接收请求,将这些对象链接成一条链,并且沿着这条链传递请求,知道有对象处理它位置
2 命令模式 模拟高档餐厅八大菜系,小二点单厨师烹饪场景 将一个请求封装成一个对象,从而使您可以用不同地请求对客户进行参数化
3 迭代器 模拟公司组织架构关系,深度迭代人员信息输出场景 提供一种方法顺序访问一个聚合对象中各个元素,而又无需暴漏该对象的内部表示
4 中介者 按照MyBatis原理手写ORM框架,给JDBC方式操作数据库增加中介者场景 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间地交互
5 备忘录 模拟互联网系统上线过程,配置文件回滚场景 在不破坏封装性地前提下,捕获一个对象地内部状态,并在该对象之外保存这个状态
6 观察者 模拟类似小客车指标摇号过程,监听消息通知用户中签场景 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
7 状态 模拟系统营销活动,状态流程审核发布上线场景 允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类
8 策略 模拟多种营销类型优惠券,折扣金额计算策略场景 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
9 模板方法 模拟爬虫各类电商商品,生成营销推广海报场景 定义一个操作中的算法估计,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构既可重新定义该算法的某些特定步骤
10 访问者 模拟家长和校长,对学生和老师的不同视角信息的访问场景 将数据结构与数据操作分离