# Java的24种设计模式与7大原则

模式是指解决某个特定领域问题，实现既定目标的方法或思想。具体来说，模式是那些身处于某个行业的从业人员根据实际的工作经验总结出的，具有通用性的且被行业公认的解决问题的方法或流程。模式并非只在软件工程中被应用，其在日常的生产活动中被广泛地使用，如制造业、餐饮业、建筑设计、医疗卫生、教育培训以及软件工程等都有模式的身影。

在软件工程中，设计模式是一种通用的、可重复使用的用于解决既定范围内普遍发生的重复性问题的软件设计方法。

使用成熟可靠的设计模式，可以提高代码复用性，节省开发时间，从而实现功能更强大、高度可维护的代码。这有助于降低软件产品的总体拥有成本，即 TCO（Total Cost of Ownership）。另一方面，由于采用了统一的标准设计方法（思想或理论知识），可以显著提升开发团队的生产效率和协作能力。

设计模式作用包括：

* 代码重用性：避免相同功能的代码多次编写。
* 可读性：使源码更易读，编程更规范，便于其他程序员的阅读和理解。
* 可扩展性：使编写的程序具有良好的可扩展性。
* 可靠性：增加新的功能后，对原来的功能没有影响。

## 七大原则

### 开闭原则

对扩展开放，对修改封闭。

在程序需要进行拓展的时候，不能去修改原有的代码，而是要扩展原有代码，实现一个热插拔的效果。

这是为了使程序的扩展性好，易于维护和升级。

### 单一职责原则

一个类负责一项职责。

不要存在多于一个导致类变更的原因，如果有，就应该把类拆分。

### 里氏替换原则（Liskov Substitution Principle）

任何基类可以出现的地方，子类一定可以出现。

**里氏代换原则是对“开-闭”原则的补充**。实现 “开闭” 原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现，所以里氏代换原则是对实现抽象化的具体步骤的规范。

里氏替换原则是继承复用的基石，只有当衍生类可以替换基类，软件单位的功能不受到影响时，基类才能真正被复用，而衍生类也能够在基类的基础上增加新的行为。

里氏替换原则中，子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构，通过这个规范的接口与外界交互，子类不应该随便破坏它。

### 依赖倒转原则（Dependence Inversion Principle）

针对接口编程，不要针对实现编程。

高层模块不应该依赖底层模块，二者都应该依赖其抽象；抽象不应该依赖细节，细节应该依赖抽象。

### 接口隔离原则（Interface Segregation Principle）

尽量细化接口，尽量减少接口中方法的数量。

每个接口中不存在子类用不到却必须实现的方法，如果不然，就要将接口拆分。使用多个单独的接口，比使用单个接口（多个接口方法集合到一个的接口）要好。

### 迪米特法则（最少知道原则）（Demeter Principle）

高内聚，低耦合，即一个类对自己依赖的类知道的越少越好。

无论被依赖的类多么复杂，都应该将逻辑封装在方法的内部，通过 public 方法提供给外部。这样当被依赖的类变化时，才能最小的影响该类。

### 合成复用原则（Composite Reuse Principle）

尽量使用合成/聚合的方式，而不是使用继承来达到复用。

## 24种设计模式

Java 语言常用的设计模式可分为三种类型：

* 创建型模式： 用来描述如何创建对象，它的主要特点是将对象的创建和使用分离。有工厂模式、抽象工厂模式、单例模式、原型模式、构建者模式。
* 结构型模式： 用来描述如何使用多个类或对象按照某种布局组合成更大的对象。有适配器模式、外观模式、过滤器模式、装饰器模式、代理模式、桥接模式、组合模式、享元模式。
* 行为型模式： 用来描述对象之间的交流以及如何分配职责。有策略模式、状态模式、观察者模式、迭代器模式、责任链模式、命令模式、解释器模式、备忘录模式、模板方法模式、访问者模式、中介者模式。

其实还有两类：并发型模式和线程池模式。

## 创建型模式（5）

### 工厂模式

工厂模式是定义了一个创建对象的接口，用于实例化类。

需要生成的对象叫做产品，生成对象的地方叫做工厂。把对象的创建和业务逻辑层分开，这样以后就避免了修改产品原代码。如果要实现新产品直接修改工厂类，而不需要在原代码中修改，这样就降低了产品原代码修改的可能性，更加容易扩展。

当一个超类具有多个子类，且需要频繁的创建子类对象时，可以采用工厂模式。

### 抽象工厂模式

抽象工厂模式是围绕一个超级工厂创建其他工厂，该超级工厂又称为其他工厂的工厂。每个生成的工厂都能按照工厂模式提供对象。

在工厂模式中，根据提供的输入参数返回产品类的实例化对象，这个过程需要通过 if-else 或者 switch 这样的逻辑判断语句来完成具体子类的判定。而在抽象工厂模式中，每种产品都有具体的工厂类与之对应，从而避免在编码过程中使用大量的逻辑判断代码。抽象工厂模式会根据输入的工厂类型以返回具体的工厂子类。抽象工厂类只负责实例化工厂子类，不参与商品子类的实例化工作。

### 单例模式

创建某个类的实例，该类的实例在系统中只有这一份。

单例模式主要是为了避免因为创建了多个实例造成资源的浪费，且多个实例由于多次调用容易导致结果出现错误，而使用单例模式能够保证整个应用中有且只有一个实例。

单例模式是 Java 中最常用，也是最简单的设计模式之一。

单例模式通常需具备如下的几个特征：

* 限制类的实例化，即系统只能存在一个该类的示例化对象。
* 必须提供一个全局可用的访问入口来获取该类的实例化对象。
* 常用于日志记录、驱动程序对象设计、缓存以及线程池。
* 也会用于其他的设计模式当中，如抽象工厂模式、建造者模式、原型模式等。

### 原型模式

原型模式是将一个对象作为原型，通过对其进行复制而克隆出多个和原型类似的新实例。

原型模式适用于类初始化需要消耗非常多的资源，这个资源包括数据、硬件资源等，通过原型拷贝避免这些消耗。比如，一个对象需要提供给其他对象访问，而且各个调用者可能都需要修改其值时，可以将原始已存在的对象通过复制（克隆）机制创建新的对象，然后根据需要，对新对象进行修改。

原型模式要求被复制的对象自身具备拷贝功能，此功能不能由外界完成。

### 构建者模式

建造者模式是将类的构建逻辑转移到类的实例化之外，使用多个简单的对象一步一步构建成一个复杂的对象。

当一个类有许多的属性，当在实例化该类的对象时，并不一定拥有该实例化对象的全部属性信息，便可使用建造者模式逐步获取实例化对象的属性信息，来完成该类的实例化过程。

建造者模式通常被用于需要多个步骤创建对象的场景中。而工厂模式和抽象工厂模式需要在实例化时获取该类实例化对象的全部属性信息。

## 结构型模式（8）

### 适配器模式

适配器模式是为使现有的多个可用接口能够在一起为客服端提供新的接口服务。在适配器模式中，负责连接不同接口的对象称为适配器。

适配器模式主要用于统一多个类的接口设计。比如，一个支付系统有三种不同的支付方式，微信支付、支付宝支付、网银支付，这三种支付的实现方法都不一样，那么就可以用适配器模式，让他们对外具有统一的方法。

### 外观模式

外观模式是为多个复杂的子系统提供一个一致的接口，使这些子系统更加容易被访问。

### 过滤器模式

过滤器模式是使用不同的标准来过滤一组对象，通过逻辑运算以解耦的方式将对象组合起来。

### 装饰器模式

修饰模式是在运行时向一个现有的对象动态的添加新的功能，同时又不改变其结构。

一般而言，为了扩展一个类经常使用继承方式实现，由于继承为类引入静态特征，并且随着扩展功能的增多，子类会很膨胀。这种情况下，就可以采用修饰模式，在程序运行时为某个对象组合新的行为。

装饰器模式是为了**增强功能**。

### 代理模式

代理模式是通过提供一个代理对象或者一个占位符来控制对实际对象的访问行为。

代理模式通常用于需要频繁操作一些复杂对象的地方，通过使用代理模式，可以借由代理类来操作目标对象，简化操作流程。

代理模式是为了**加以控制**。

### 桥接模式

桥接模式是将抽象与行为实现相分离，以实现结构上的解耦，保持各部分的独立性以及功能扩展。

桥接模式的实现优先遵循组合而不是继承。

### 组合模式

组合模式是依据树形结构来组合对象，用来表示部分以及整体层次，让整体与局部具有相同的行为。

比如，目录和文件：目录可以只包含子目录，也可以只包含文件，当然可以同时包含子目录和文件；而子目录又可以包含子目录或者文件。

### 享元模式

享元模式是通过共享对象来有效的支持大量细粒度对象的复用。

细粒度的对象都具备较接近的外部状态，而且内部状态与环境无关，也就是说对象没有特定身份。

当需要创建一个类的很多对象时，可以使用享元模式，通过共享对象信息来减轻内存负载。比如：池化技术，数据库连接池 、线程池等。

## 行为型模式（11）

### 策略模式

策略模式定义了一系列的算法，把它们一个个封装起来，并且使它们可相互替换。

策略模式可以让一组策略共存，代码互不干扰，它不仅将选择策略的逻辑从策略本身中分离出来，还组织和简化了代码。

> 计出在我，取之在君。

### 状态模式

状态模式是根据对象的状态改变其行为。

如果必须根据对象的状态改变对象的行为，可以在对象中定义一个状态变量，并使用逻辑判断语句块（如 if-else）根据状态执行不同的操作。

> switch 语句

### 观察者模式

观察者模式是当被观察者（被观察/监听的目标）被修改时，则会自动通知观察者（依赖它的对象）。

观察者，顾名思义，就是一个监听者，类似监听器的存在，用于时时关注一个对象的状态是否发生变化。

观察者模式的目的是在对象之间定义一对多的依赖关系。被观察者会收集依赖它的观察者，当被观察者状态发生改变时，它会通知所有观察者，而观察者可以根据新状态做出相应的反应。

> 游戏：我方水晶正在被攻击...

### 迭代器模式

迭代器模式提供一个标准的方法用于顺序访问一个聚合对象中的各个元素。

迭代器模式可以为遍历不同的集合结构，提供统一接口。它不仅仅是遍历集合，还可以根据不同的要求提供不同类型的迭代器，然后通过集合隐藏内部的遍历细节。

### 责任链模式

责任链模式是为某个请求创建一个对象链，每个对象依序对其进行处理或者将它传给链中的下一个对象。

try-catch 就是一个典型的责任链模式的应用案例。在 try-catch 语句中，可以同时存在多个 catch 语句块，每个 catch 语句块都是处理该特定异常的处理器。当 try 语句块中发生异常是，异常将被发送到 catch 语句块进行处理，如果这个语句块无法处理它，它将会被请求转发到链中的下一个 catch 语句块。如果所有 catch 语句块仍然不能处理该异常，则该异常将会被向上抛出。

> 击鼓传花，鼓停花止。

### 命令模式

命令模式是将一个请求封装为一个对象，使发出请求的责任和执行请求的责任分割开。

在有些时候我们无法控制方法被执行的时机和上下文信息。在这种情况下，可以将方法封装到对象的内部，通过在对象内部存储调用方所需要的信息，就可以让客户端或者服务自由决定何时调用方法。

> cp -r dir1 dir2

### 解释器模式

解释器模式提供语言的语法或表达式，实现了一个表达式接口，该接口解释一个特定的上下文，比如，SQL 解析、符号处理引擎等。

> SELECT id, name FROM user

### 备忘录模式

备忘录模式是为对象的状态提供存储和恢复功能。

备忘录模式由两个对象来实现：Originator 和 Caretaker。Originator 需要具有保存和恢复对象状态的能力，它使用内部类来保存对象的状态。内部类则称为备忘录，因为它是对象私有的，因此其他类不能直接访问它。

备忘录模式适用于需要保存和恢复数据的相关状态场景，比如，Windows 里的 ctri + z、IE 中的后退、数据库连接的事务管理。

> 月光宝盒，让时间回到过去。

### 模板方法模式

模板方法模式定义了操作中的算法的骨架，而将一些步骤延迟到子类中，使得子类可以不改变一个算法的结构，就可以重定义该算法的某些特定步骤。

模板方法模式主要用于多个子类有共同的方法，且逻辑基本相同，此时可以把核心的算法和重要的功能设计为模板方法，子类去实现相关细节功能。

### 访问者模式

访问者模式使用了一个访问者类，它可以改变类中各元素的执行算法。通过这种方式，访问者对象就可以处理元素对象上的操作。

访问者模式适用于对象结构比较稳定，但经常需要在此对象结构上定义新的操作。

> 别客气，当成自己家，想吃什么自己拿。

### 中介者模式

中介者模式提供了一个中介类，该类通常处理不同类之间的通信，并支持松耦合，使代码易于维护。。

中介者模式适用于当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用场景。

> 您的顺丰快递、圆通快递已到达菜鸟驿站，请及时取件。

## 参考资料

[24种设计模式介绍](https://www.jianshu.com/p/b676b8775041)

[24种Java常用设计模式基本原理导读](https://www.51cto.com/article/599573.html)

[Java设计模式：23种设计模式(万字图文全面总结)](https://www.163.com/dy/article/H1HNLOLD05421TE1.html)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lizh.gitbook.io/knowledge/tech/17java-de-24-zhong-she-ji-mo-shi-yu-7-da-yuan-ze.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
