mirror of
https://github.com/wahyd4/daily.git
synced 2026-08-09 05:16:16 +10:00
设计模式
This commit is contained in:
+54
-54
@@ -1,62 +1,62 @@
|
||||
《[设计模式之美](https://time.geekbang.org/column/intro/250)》是极客时间上的一个代码学习系列,在学习之后特在此做记录和总结。
|
||||
  《[设计模式之美](https://time.geekbang.org/column/intro/250)》是极客时间上的一个代码学习系列,在学习之后特在此做记录和总结。
|
||||
|
||||
## 一、设计原则
|
||||
|
||||
**1)单一职责原则(Single Responsibility Principle,SRP)**
|
||||
|
||||
一个类或者模块只负责完成一个职责(或者功能),模块可看作比类更加粗粒度的代码块,模块中包含多个类,多个类组成一个模块。
|
||||
  一个类或者模块只负责完成一个职责(或者功能),模块可看作比类更加粗粒度的代码块,模块中包含多个类,多个类组成一个模块。
|
||||
|
||||
**2)开闭原则(Open Closed Principle,OCP)**
|
||||
|
||||
添加一个新的功能,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
|
||||
  添加一个新的功能,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
|
||||
|
||||
**3)里式替换原则(Liskov Substitution Principle,LSP)**
|
||||
|
||||
子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。
|
||||
  子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。
|
||||
|
||||
**4)接口隔离原则(Interface Segregation Principle,ISP)**
|
||||
|
||||
接口的调用者或使用者不应该强迫依赖它不需要的接口。
|
||||
  接口的调用者或使用者不应该强迫依赖它不需要的接口。
|
||||
|
||||
**5)依赖倒置原则(Dependency Inversion Principle,DIP)**
|
||||
|
||||
高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。抽象不要依赖具体实现细节,具体实现细节依赖抽象。
|
||||
  高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。抽象不要依赖具体实现细节,具体实现细节依赖抽象。
|
||||
|
||||
**6)控制反转(Inversion Of Control,IOC)**
|
||||
|
||||
“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。
|
||||
  “控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。
|
||||
|
||||
在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员“反转”到了框架。
|
||||
  在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员“反转”到了框架。
|
||||
|
||||
**7)依赖注入(Dependency Injection,DI)**
|
||||
|
||||
不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
|
||||
  不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
|
||||
|
||||
**8)基于接口而非实现编程**
|
||||
|
||||
设计初衷是将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。
|
||||
  设计初衷是将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。
|
||||
|
||||
**9)KISS**
|
||||
|
||||
尽量保持简单。代码足够简单,也就意味着很容易读懂,bug 比较难隐藏。原则讲的是“如何做”的问题。
|
||||
  尽量保持简单。代码足够简单,也就意味着很容易读懂,bug 比较难隐藏。原则讲的是“如何做”的问题。
|
||||
|
||||
**10)YAGNI(You Ain’t Gonna Need It)**
|
||||
|
||||
不要去设计当前用不到的功能;不要去编写当前用不到的代码。其核心思想就是:不要做过度设计。原则说的是“要不要做”的问题。
|
||||
  不要去设计当前用不到的功能;不要去编写当前用不到的代码。其核心思想就是:不要做过度设计。原则说的是“要不要做”的问题。
|
||||
|
||||
**11)DRY(Don’t Repeat Yourself)**
|
||||
|
||||
三种典型的代码重复情况:实现逻辑重复、功能语义重复和代码执行重复。
|
||||
  三种典型的代码重复情况:实现逻辑重复、功能语义重复和代码执行重复。
|
||||
|
||||
**12)迪米特法则(Law of Demeter,LOD)**
|
||||
|
||||
也叫最小知识原则(The Least Knowledge Principle),就是不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。
|
||||
  也叫最小知识原则(The Least Knowledge Principle),就是不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。
|
||||
|
||||
## 二、规范与重构
|
||||
|
||||
**1)可测试性**
|
||||
|
||||
针对代码编写单元测试的难易程度。通过依赖注入,在编写单元测试的时候,可以通过 mock 的方法依赖外部服务。
|
||||
  针对代码编写单元测试的难易程度。通过依赖注入,在编写单元测试的时候,可以通过 mock 的方法依赖外部服务。
|
||||
|
||||
**2)解耦**
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
**4)代码质量问题清单**
|
||||
|
||||
下面 7 个是通用的关注点,可以作为一些常规检查项,套用在任何代码的重构上。
|
||||
  下面 7 个是通用的关注点,可以作为一些常规检查项,套用在任何代码的重构上。
|
||||
1. 目录设置是否合理、模块划分是否清晰、代码结构是否满足“高内聚、松耦合”?
|
||||
2. 是否遵循经典的设计原则和设计思想(SOLID、DRY、KISS、YAGNI、LOD 等)?
|
||||
3. 设计模式是否应用得当?是否有过度设计?
|
||||
@@ -93,7 +93,7 @@
|
||||
6. 代码是否容易测试?单元测试是否全面覆盖了各种正常和异常的情况?
|
||||
7. 代码是否易读?是否符合编码规范(比如命名和注释是否恰当、代码风格是否一致等)?
|
||||
|
||||
还要关注代码实现是否满足业务本身特有的功能和非功能需求。一些比较共性的关注点如下所示:
|
||||
  还要关注代码实现是否满足业务本身特有的功能和非功能需求。一些比较共性的关注点如下所示:
|
||||
1. 代码是否实现了预期的业务需求?
|
||||
2. 逻辑是否正确?是否处理了各种异常情况?
|
||||
3. 日志打印是否得当?是否方便 debug 排查问题?
|
||||
@@ -106,51 +106,51 @@
|
||||
|
||||
**1)单例设计模式(Singleton Design Pattern)**
|
||||
|
||||
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类。用来创建全局唯一的对象。
|
||||
  一个类只允许创建一个对象(或者实例),那这个类就是一个单例类。用来创建全局唯一的对象。
|
||||
|
||||
**2)工厂模式**
|
||||
|
||||
用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
|
||||
  用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
|
||||
1. 简单工厂(Simple Factory)利用 if 分支创建对象。
|
||||
2. 工厂方法(Factory Method)定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
|
||||
|
||||
**3)建造者模式(Builder Design Pattern)**
|
||||
|
||||
建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。
|
||||
  建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。
|
||||
|
||||
**4)原型设计模式(Prototype Design Pattern)**
|
||||
|
||||
基于原型来创建对象的方式。利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。
|
||||
  基于原型来创建对象的方式。利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。
|
||||
|
||||
## 四、结构型设计模式
|
||||
|
||||
**1)代理模式(Proxy Design Pattern)**
|
||||
|
||||
在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
|
||||
  在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。
|
||||
|
||||
**2)桥接模式(Bridge Design Pattern)**
|
||||
|
||||
对于这个模式有两种不同的理解方式。
|
||||
  对于这个模式有两种不同的理解方式。
|
||||
1. 将抽象和实现解耦,让它们可以独立变化。
|
||||
2. 一个类存在两个(或多个)独立变化的维度,通过组合的方式,让这两个(或多个)维度可以独立进行扩展。
|
||||
|
||||
**3)装饰器模式(Decorator Design Pattern)**
|
||||
|
||||
相对于简单的组合关系,有两个比较特殊的地方。
|
||||
  相对于简单的组合关系,有两个比较特殊的地方。
|
||||
1. 装饰器类和原始类继承同样的父类,这样可以对原始类“嵌套”多个装饰器类。
|
||||
2. 装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。
|
||||
|
||||
代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。
|
||||
  代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。
|
||||
|
||||
代理模式偏重业务无关,高度抽象和稳定性较高的场景。装饰器模式偏重业务相关,定制化诉求高,改动较频繁的场景。
|
||||
  代理模式偏重业务无关,高度抽象和稳定性较高的场景。装饰器模式偏重业务相关,定制化诉求高,改动较频繁的场景。
|
||||
|
||||
**4)适配器模式(Adapter Design Pattern)**
|
||||
|
||||
将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
|
||||
  将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。
|
||||
|
||||
适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。
|
||||
  适配器模式可以看作一种“补偿模式”,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。
|
||||
|
||||
代理、桥接、装饰器和适配器都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。
|
||||
  代理、桥接、装饰器和适配器都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。
|
||||
1. 代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
|
||||
2. 桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
|
||||
3. 装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
|
||||
@@ -158,82 +158,82 @@
|
||||
|
||||
**5)门面模式(Facade Design Pattern)**
|
||||
|
||||
为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。子系统(subsystem)既可以是一个完整的系统,也可以是更细粒度的类或者模块。
|
||||
  为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。子系统(subsystem)既可以是一个完整的系统,也可以是更细粒度的类或者模块。
|
||||
|
||||
与适配器模式的区别:
|
||||
  与适配器模式的区别:
|
||||
1. 适配器模式是做接口转换,解决的是原接口和目标接口不匹配的问题。在代码结构上主要是继承加组合。
|
||||
2. 门面模式做接口整合,解决的是多接口调用带来的问题。在代码结构上主要是封装。
|
||||
|
||||
**6)组合模式(Composite Design Pattern)**
|
||||
|
||||
组合模式是将一组对象组织成树形结构,以表示一种“部分 - 整体”的层次结构。组合让客户端(指代码的使用者)可以统一单个对象和组合对象的处理逻辑。
|
||||
  组合模式是将一组对象组织成树形结构,以表示一种“部分 - 整体”的层次结构。组合让客户端(指代码的使用者)可以统一单个对象和组合对象的处理逻辑。
|
||||
|
||||
业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树,业务需求可以通过在树上的递归遍历算法来实现。
|
||||
  业务场景的一种数据结构和算法的抽象。其中,数据可以表示成树,业务需求可以通过在树上的递归遍历算法来实现。
|
||||
|
||||
**7)享元模式(Flyweight Design Pattern)**
|
||||
|
||||
意图是复用对象,节省内存,前提是享元对象是不可变对象。
|
||||
  意图是复用对象,节省内存,前提是享元对象是不可变对象。
|
||||
|
||||
如果这些重复的对象是不可变对象,就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。
|
||||
  如果这些重复的对象是不可变对象,就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。
|
||||
|
||||
不可变对象不能暴露任何 set() 等修改内部状态的方法。
|
||||
  不可变对象不能暴露任何 set() 等修改内部状态的方法。
|
||||
|
||||
## 五、行为型设计模式
|
||||
|
||||
**1)观察者模式(Observer Design Pattern)**
|
||||
|
||||
也叫发布订阅模式(Publish-Subscribe Design Pattern),在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
|
||||
  也叫发布订阅模式(Publish-Subscribe Design Pattern),在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
|
||||
|
||||
**2)模板模式(Template Method Design Pattern)**
|
||||
|
||||
可在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
|
||||
  可在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
|
||||
|
||||
**3)策略模式(Strategy Design Pattern)**
|
||||
|
||||
定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。
|
||||
  定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。
|
||||
|
||||
策略模式解耦的是策略的定义、创建、使用这三部分。让每个部分都不至于过于复杂、代码量过多。
|
||||
  策略模式解耦的是策略的定义、创建、使用这三部分。让每个部分都不至于过于复杂、代码量过多。
|
||||
|
||||
**4)职责链模式(Chain Of Responsibility Design Pattern)**
|
||||
|
||||
将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
|
||||
  将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
|
||||
|
||||
**5)状态模式**
|
||||
|
||||
状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。
|
||||
  状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。
|
||||
|
||||
状态模式通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑。
|
||||
  状态模式通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑。
|
||||
|
||||
**6)迭代器模式(Iterator Design Pattern)**
|
||||
|
||||
也叫游标模式(Cursor Design Pattern)用来遍历集合对象。
|
||||
  也叫游标模式(Cursor Design Pattern)用来遍历集合对象。
|
||||
|
||||
迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
|
||||
  迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
|
||||
|
||||
迭代器中需要定义 hasNext()、currentItem()、next() 三个最基本的方法。
|
||||
  迭代器中需要定义 hasNext()、currentItem()、next() 三个最基本的方法。
|
||||
|
||||
**7)访问者者模式(Visitor Design Pattern)**
|
||||
|
||||
允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。
|
||||
  允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。
|
||||
|
||||
**8)备忘录模式(Memento Design Pattern)**
|
||||
|
||||
也叫快照(Snapshot)模式,在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
|
||||
  也叫快照(Snapshot)模式,在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
|
||||
|
||||
**9)命令模式(Command Design Pattern)**
|
||||
|
||||
将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。
|
||||
  将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。
|
||||
|
||||
在大部分编程语言中,函数没法作为参数传递给其他函数,也没法赋值给变量。借助命令模式,可以将函数封装成对象。设计一个包含这个函数的类,实例化一个对象传来传去,这样就可以实现把函数像对象一样使用。
|
||||
  在大部分编程语言中,函数没法作为参数传递给其他函数,也没法赋值给变量。借助命令模式,可以将函数封装成对象。设计一个包含这个函数的类,实例化一个对象传来传去,这样就可以实现把函数像对象一样使用。
|
||||
|
||||
在策略模式中,不同的策略具有相同的目的、不同的实现、互相之间可以替换。而在命令模式中,不同的命令具有不同的目的,对应不同的处理逻辑,并且互相之间不可替换。
|
||||
  在策略模式中,不同的策略具有相同的目的、不同的实现、互相之间可以替换。而在命令模式中,不同的命令具有不同的目的,对应不同的处理逻辑,并且互相之间不可替换。
|
||||
|
||||
**10)解释器模式(Interpreter Design Pattern)**
|
||||
|
||||
只在一些特定的领域会被用到,比如编译器、规则引擎、正则表达式。它能为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
|
||||
  只在一些特定的领域会被用到,比如编译器、规则引擎、正则表达式。它能为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
|
||||
|
||||
**11)中介模式(Mediator Design Pattern)**
|
||||
|
||||
定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。
|
||||
  定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。
|
||||
|
||||
中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。
|
||||
  中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。
|
||||
Reference in New Issue
Block a user