新增设计模式

pull/2/head
吴尤 4 years ago
parent 4ba2d4abbc
commit fc997da84b

@ -0,0 +1,205 @@
## 引言
### 庞大的跨平台图像浏览系统
> 实例说明
某软件公司要开发一个**跨平台图像浏览系统**要求该系统能够显示BMP、JPG、GIF、PNG等**多种格式**的文件并且能够在Windows、Linux、Unix等**多个操作系统**上运行。系统首先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。
> 初始设计方案
![image-20211201143703180](设计模式/image-20211201143703180.png)
> 问题
- 采用了多层继承结构,导致系统中类的个数急剧增加,**具体层的类的个数 = 所支持的图像文件格式数×所支持的操作系统数**
- 系统扩展麻烦,无论是增加新的图像文件格式还是增加新的操作系统,都需要增加大量的具体类,这将**导致系统变得非常庞大,增加运行和维护开销**
### 不够灵活的影院售票系统
> 实例说明
- 某软件公司为某电影院开发了一套影院售票系统,在该系统中需要**为不同类型的用户提供不同的电影票打折方式**,具体打折方案如下:
- 学生凭学生证可享受票价8折优惠
- 年龄在10周岁及以下的儿童可享受每张票减免10元的优惠原始票价需大于等于20元
- 影院VIP用户除享受票价半价优惠外还可进行积分积分累计到一定额度可换取电影院赠送的奖品。
- 该系统在将来可能还要根据需要引入新的打折方式。
> 初始实现方案
```java
public class MovieTicket {
private double price;
//compute the price
public double calculate(String type) {
//student ticket
if(type.equalsIgnoreCase("student")) {
return this.price * 0.8;
}
//children ticket
else if(type.equalsIgnoreCase("children") && this.price >= 20 ) {
return this.price - 10;
}
//VIP ticket
else if(type.equalsIgnoreCase("vip")) {
//add points, code is omitted
return this.price * 0.5;
}
else {
return this.price;
}
}
}
```
> 问题
- MovieTicket类的**calculate()方法非常庞大**,它包含各种打折算法的实现代码,在代码中出现了**较长的条件转移语句,不利于测试和维护**
- 在增加新的打折算法或者对原有打折算法进行修改时必须修改MovieTicket类的源代码**系统的灵活性和可扩展性较差**
- **算法的复用性差**,如果另一个系统需要重用某些打折算法,只能通过对源代码进行复制粘贴来重用,无法单独重用其中的某个或某些算法
### 重用第三方算法库时面临的问题
> 实例说明
- 某软件公司在开发一个银行业务处理系统时需要对其中的机密数据进行加密处理,通过分析发现,用于加密的程序已经存在于一个第三方算法库中,但是**没有该算法库的源代码**。在系统初始设计阶段已定义数据操作接口DataOperation且该接口已被很多同事使用对该接口的修改势必导致大量代码需要产生改动。
> 问题
- 如何在既不修改现有接口又不需要算法库源代码的基础上能够实现第三方算法库的重用是该软件公司开发人员必须面对的问题。
![image-20211201144526566](设计模式/image-20211201144526566.png)
## 设计模式的诞生与发展
> 模式的诞生与定义
- 模式(Pattern)起源于**建筑业**而非软件业
- 模式之父——美国加利佛尼亚大学环境结构中心研究所所长**Christopher Alexander博士**
- 《A Pattern Language: Towns, Buildings, Construction》——253个建筑和城市规划模式
- 模式
- **Context**(模式可适用的前提条件)
- **Theme或Problem**(在特定条件下要解决的目标问题)
- **Solution**(对目标问题求解过程中各种物理关系的记述)
![image-20211201144705365](设计模式/image-20211201144705365.png)
- Alexander给出了关于模式的经典定义
- 每个模式都描述了一个**在我们的环境中不断出现的问题**,然后描述了该问题的**解决方案的核心**,通过这种方式,人们可以无数次地**重用那些已有的解决方案**,无须再重复相同的工作
模式是**在特定环境下**人们解决某类重复出现**问题**的一套成功或有效的**解决方案**。
A pattern is a successful or efficient **solution** to a recurring **problem** within a **context**.
> 软件设计模式的诞生
- 20世纪80年代末软件工程界开始关注Christopher Alexander等在这一住宅、公共建筑与城市规划领域的重大突破
- “四人组(**Gang of FourGoF分别是Erich Gamma, Richard Helm, Ralph Johnson和John Vlissides**)”于1994年归纳发表了23种在软件开发中使用频率较高的设计模式旨在**用模式来统一沟通面向对象方法在分析、设计和实现间的鸿沟**
![image-20211201145006809](设计模式/image-20211201145006809.png)
![image-20211201145048290](设计模式/image-20211201145048290.png)
- 软件模式:**在一定条件下的软件开发问题及其解法**
- 问题描述
- 前提条件(环境或约束条件)
- 解法
- 效果
![image-20211201145207910](设计模式/image-20211201145207910.png)
- **大三律(Rule of Three)**
- 只有经过**3个以上不同类型或不同领域的系统**的校验,一个解决方案才能从候选模式升格为模式
> 设计模式的发展
- 1987年Kent Beck和Ward Cunningham借鉴Alexander的模式思想在程序开发中开始应用一些模式 在OOPSLA会议上发表了他们的成果
- 1990年OOPSLA与ECOOP联合举办Erich Gamma和Richard Helm等人开始讨论有关模式的话题(Bruce Anderson主持),“四人组” 正式成立,并开始着手进行设计模式的分类整理工作
- 1991 年OOPSLABruce Anderson主持了首次针对设计模式的研讨会
- 1992 年OOPSLA Anderson再度主持研讨会模式已经逐渐成为人们讨论的话题
- 注: OOPSLA (Object-Oriented Programming, Systems, Languages & Applications面向对象编程、系统、语言和应用大会)编程语言及软件工程国际顶级会议2010年改为SPLASH --- Systems, Programming, Languages and Applications: Software for Humanity
- 1993年Kent Beck 和 Grady Booch 赞助了第一次关于设计模式的会议这个设计模式研究组织发展成为著名的Hillside Group研究组
- 1994 年由Hillside Group发起在美国伊利诺伊州(Illinois)的Allerton Park召开了第1届关于面向对象模式的世界性会议名为PLoP(Pattern Languages of Programs, 编程语言模式会议)简称PLoP94
- 1995年PLoP95 仍在伊利诺伊州的Allerton Park举行 ,“四人组”出版了《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书本书成为1995年最抢手的面向对象书籍也成为设计模式的经典书籍
- 从1995年至今设计模式在软件开发中得以广泛应用在**Sun的Java SE/Java EE平台**和**Microsoft的.NET平台设**计中应用了大量的设计模式
- 轻量级框架Struts、Spring、Hibernate、JUnit、NHibernate、NUnit ……
- 语言C++、Java、C#、Objective-C、 VB.net、Smalltalk、PHP、 Delphi、JavaScript、Ruby……
- 得到越来越多的企业和高校的关注与重视
- 越来越多的书籍和网站
## 设计模式的定义与分类
> 设计模式的定义
- **设计模式(Design Pattern)**
- 一套**被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的**总结
- 是一种用于对软件系统中不断重现的设计问题的**解决方案**进行**文档化**的技术
- 是一种**共享专家设计经验**的技术
- 目的:**为了可重用代码、让代码更容易被他人理解、提高代码可靠性**
设计模式是在**特定环境下**为解决**某一通用软件设计问题**提供的**一套定制的解决方案**,该方案描述了对象和类之间的相互作用。
Design patterns are descriptions of communicating objects and classes that are customized to **solve** a general **design problem** in a particular **context.**
> 设计模式的基本要素
- 设计模式一般包含模式名称、问题、目的、解决方案、效果、实例代码和相关设计模式等基本要素,**4个关键要素**如下:
- **模式名称** (Pattern Name)
- **问题** (Problem)
- **解决方案** (Solution)
- **效果** (Consequences)
> 设计模式的分类
- 根据**目的**(模式是用来做什么的)可分为**创建型(Creational)****结构型(Structural)**和**行为型(Behavioral)**三类:
- **创建型模式**主要用于**创建对象**
- **结构型模式**主要用于**处理类或对象的组合**
- **行为型模式**主要用于**描述类或对象如何交互和怎样分配职责**
- 根据**范围**,即模式主要是**处理类之间的关系**还是**处理对象之间的关系**,可分为**类模式**和**对象模式**两种:
- **类模式处理类和子类之间的关系**,这些关系通过继承建立,在编译时刻就被确定下来,是一种**静态关系**
- **对象模式处理对象间的关系**,这些关系在运行时变化,更具动态性
![image-20211201150612176](设计模式/image-20211201150612176.png)
## GoF设计模式简介
### 创建型模式
- 抽象工厂模式(Abstract Factory) ★★★★★
- 建造者模式(Builder) ★★☆☆☆
- 工厂方法模式(Factory Method) ★★★★★
- 原型模式(Prototype) ★★★☆☆
- 单例模式(Singleton) ★★★★☆
### 结构性模式
- 适配器模式(Adapter) ★★★★☆
- 桥接模式(Bridge) ★★★☆☆
- 组合模式(Composite) ★★★★☆
- 装饰模式(Decorator) ★★★☆☆
- 外观模式(Facade) ★★★★★
- 享元模式(Flyweight) ★☆☆☆☆
- 代理模式(Proxy) ★★★★☆
### 行为型模式
- 职责链模式(Chain of Responsibility) ★★☆☆☆
- 命令模式(Command) ★★★★☆
- 解释器模式(Interpreter) ★☆☆☆☆
- 迭代器模式(Iterator) ★★★★★
- 中介者模式(Mediator) ★★☆☆☆
- 备忘录模式(Memento) ★★☆☆☆
- 观察者模式(Observer) ★★★★★
- 状态模式(State) ★★★☆☆
- 策略模式(Strategy) ★★★★☆
- 模板方法模式(Template Method) ★★★☆☆
- 访问者模式(Visitor) ★☆☆☆☆
## 设计模式的优点
- **融合了众多专家的经验**,并以一种标准的形式供广大开发人员所用
- 提供了一套**通用的设计词汇和一种通用的语言**,以方便开发人员之间进行沟通和交流,使得设计方案更加通俗易懂
- 让人们可以更加简单方便地**复用成功的设计和体系结构**
- 使得设计方案更加**灵活**,且易于**修改**
- 将提高软件系统的**开发效率和软件质量**,且在一定程度上节约设计成本
- 有助于初学者更深入地**理解面向对象思想**,方便阅读和学习现有类库与其他系统中的源代码,还可以提高软件的设计水平和代码质量

@ -0,0 +1,209 @@
## 面向对象设计原则概述
- **可维护性(Maintainability):指软件能够被理解、改正、适应及扩展的难易程度**
- **可复用性(Reusability):指软件能够被重复使用的难易程度**
- 面向对象设计的目标之一在于**支持可维护性复用**,一方面需要实现设计方案或者源代码的复用,另一方面要确保系统能够易于扩展和修改,具有良好的可维护性
- 面向对象设计原则为**支持可维护性复用而诞生**
- **指导性原则,非强制性原则**
- 每一个设计模式都符合一个或多个面向对象设计原则,面向对象设计原则是**用于评价一个设计模式的使用效果的重要指标之一**
| **设计原则名称** | **定** **义** | **使用频率** |
| ------------------------------------------------------ | ------------------------------------------------------------ | ------------ |
| **单一职责原则(Single Responsibility Principle, SRP)** | **一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中** | ★★★★☆ |
| **开闭原则(Open-Closed Principle, OCP)** | **软件实体应当对扩展开放,对修改关闭** | ★★★★★ |
| **里氏代换原则(LiskovSubstitution Principle, LSP)** | **所有引用基类的地方必须能透明地使用其子类的对象** | ★★★★★ |
| **依赖倒转原则(Dependence Inversion Principle, DIP)** | **高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象** | ★★★★★ |
| **接口隔离原则(Interface Segregation Principle, ISP)** | **客户端不应该依赖那些它不需要的接口** | ★★☆☆☆ |
| **合成复用原则(Composite Reuse Principle, CRP)** | **优先使用对象组合,而不是继承来达到复用的目的** | ★★★★☆ |
| **迪米特法则(Law of Demeter,LoD)** | **每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位** | ★★★☆☆ |
## 单一职责原则
> 单一职责原则定义
**单一职责原则**:一个对象应该**只包含单一的职责**,并且该职责被完整地封装在一个类中。
**Single Responsibility Principle (SRP)**: Every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class.
- 单一职责原则是最简单的面向对象设计原则,**用于控制类的粒度大小**
- 就一个类而言,应该**仅有一个引起它变化的原因**
- There should never be more than **one reason for a class to change**.
> 单一职责原则分析
- 一个类(大到模块,小到方法)**承担的职责越多**,它**被复用的可能性就越小**
- **当一个职责变化时**,可能**会影响其他职责的运作**
- 将这些职责进行分离,**将不同的职责封装在不同的类中**
- 将不同的变化原因封装在不同的类中
- 单一职责原则**是实现高内聚、低耦合的指导方针**
> 单一职责原则实例
- 实例说明
某软件公司开发人员针对CRMCustomer Relationship Management客户关系管理系统中的客户信息图表统计模块提出了如图所示的初始设计方案。
![image-20211201152311165](设计模式/image-20211201152311165.png)
getConnection()方法用于连接数据库findCustomers()用于查询所有的客户信息createChart()用于创建图表displayChart()用于显示图表。
现使用单一职责原则对其进行重构。
- 实例解析
![image-20211201152431086](设计模式/image-20211201152431086.png)
## 开闭原则
> 开闭原则定义
**开闭原则**:软件实体应当**对扩展开放,对修改关闭**。
**Open-Closed Principle (OCP)**: Software entities should be **open for extension**, but **closed for modification.**
- 开闭原则是**面向对象的可复用设计的第一块基石**,是最重要的面向对象设计原则
- 开闭原则由**Bertrand Meyer**于1988年提出
- 在开闭原则的定义中,软件实体可以是**一个软件模块**、**一个由多个类组成的局部结构**或**一个独立的类**
- 开闭原则是指**软件实体应尽量在不修改原有代码的情况下进行扩展**
> 开闭原则分析
- **抽象化**是开闭原则的关键
- 相对稳定的**抽象层** + 灵活的**具体层**
- **对可变性封装原则(Principle of Encapsulation of Variation, EVP)**:找到系统的可变因素并将其封装起来
## 里氏代换原则
> 里氏替换原则定义
**里氏代换原则**:所有引用**基类**的地方必须能透明地使用其**子类**的对象。如果对每一个类型为S的对象o1都有类型为T的对象o2使得以T定义的所有程序P在所有的对象o1都代换o2时程序P的行为没有变化那么类型S是类型T的子类型。
**Liskov Substitution Principle (LSP)**: Functions that use pointers or references to **base classes** must be able to use objects of **derived classes** without knowing it.If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
> 里氏替换原则分析
- 里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士、麻省理工学院教授Barbara Liskov和卡内基.梅隆大学Jeannette Wing教授于1994年提出
![image-20211201153154215](设计模式/image-20211201153154215.png)
- 在软件中**将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常**,反过来则不成立。如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象
- 在程序中**尽量使用基类类型来对对象进行定义**,而在运行时再确定其子类类型
![image-20211201153241148](设计模式/image-20211201153241148.png)
## 依赖倒转原则
> 依赖倒转原则定义
**依赖倒转原则**:高层模块不应该依赖低层模块,它们都应该依赖抽象。**抽象不应该依赖于细节,细节应该依赖于抽象。**
**Dependency Inversion Principle (DIP)**: High level modules should not depend upon low level modules, both should depend upon abstractions. **Abstractions should not depend upon details, details should depend upon abstractions.**
- 要**针对接口编程,不要针对实现编程**
- **Program to an interface, not an implementation.**
> 依赖倒转原则分析
- 依赖倒转原则是**Robert C. Martin**在1996年为“C++ Reporter”所写的专栏Engineering Notebook的第三篇后来加入到他在2002年出版的经典著作《Agile Software Development, Principles, Patterns, and Practices》一书中
- 在程序代码中传递参数时或在关联关系中,**尽量引用层次高的抽象层类**,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等
- **在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中**
- 针对抽象层编程,将具体类的对象通过**依赖注入(Dependency Injection, DI)**的方式注入到其他对象
- 构造注入
- 设值注入Setter注入
- 接口注入
>OCP/LSP/DIP综合实例
- 实例 说明
某软件公司开发人员在开发CRM系统时发现该系统经常需要将存储在TXT或Excel文件中的客户信息转存到数据库中因此需要进行数据格式转换。在客户数据操作类CustomerDAO中将调用数据格式转换类的方法来实现格式转换初始设计方案结构如图所示
![image-20211201153807810](设计模式/image-20211201153807810.png)
该软件公司开发人员发现该设计方案存在一个非常严重的问题由于每次转换数据时数据来源不一定相同因此需要经常更换数据转换类例如有时候需要将TXTDataConvertor改为ExcelDataConvertor此时需要修改CustomerDAO的源代码而且在引入并使用新的数据转换类时也不得不修改CustomerDAO的源代码系统扩展性较差违反了开闭原则现需要对该方案进行重构。
- 实例解析
![image-20211201153852785](设计模式/image-20211201153852785.png)
## 接口隔离原则
> 接口隔离原则定义
**接口隔离原则**:客户端**不应该依赖那些它不需要的接口。** **Interface Segregation Principle (ISP)**: Clients **should not be forced to depend upon interfaces that they do not use.**
> 接口隔离原则分析
- 当一个接口太大时,需要将它**分割成一些更细小的接口**
- 使用该接口的客户端**仅需知道与之相关的方法**即可
- 每一个接口应该**承担一种相对独立的角色**,不干不该干的事,该干的事都要干
- **“接口”定义(1)****一个类型所提供的所有方法特征的集合。**一个接口代表一个角色,每个角色都有它特定的一个接口,“**角色隔离原则**”
- **“接口”定义(2)****狭义的特定语言的接口。**接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口,每个接口中只包含一个客户端所需的方法,“**定制服务**”
> 接口隔离原则实例
- 实例说明
某软件公司开发人员针对CRM系统的客户数据显示模块设计了如图所示CustomerDataDisplay接口其中方法readData()用于从文件中读取数据方法transformToXML()用于将数据转换成XML格式方法createChart()用于创建图表方法displayChart()用于显示图表方法createReport()用于创建文字报表方法displayReport()用于显示文字报表。
![image-20211201154403705](设计模式/image-20211201154403705.png)
在实际使用过程中开发人员发现该接口很不灵活例如如果一个具体的数据显示类无须进行数据转换源文件本身就是XML格式但由于实现了该接口不得不实现其中声明的transformToXML()方法(至少需要提供一个空实现);如果需要创建和显示图表,除了需要实现与图表相关的方法外,还需要实现创建和显示文字报表的方法,否则程序在编译时将报错。
现使用接口隔离原则对其进行重构。
- 实例解析
![image-20211201154433079](设计模式/image-20211201154433079.png)
## 合成复用原则
> 合成复用原则定义
**合成复用原则**:优先**使用对象组合,而不是继承**来达到**复用**的目的。
**Composite Reuse Principle (CRP)****Favor composition** of objects **over inheritance** as a **reuse** mechanism.
> 合成复用原则分析
- 合成复用原则就是在一个新的对象里通过**关联关系(包括组合关系和聚合关系)**来使用一些已有的对象,使之成为新对象的一部分
- 新对象**通过委派调用已有对象的方法**达到复用功能的目的
- 复用时**要尽量使用组合/聚合关系(关联关系),少用继承**
- **继承复用**:实现简单,易于扩展。破坏系统的封装性;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。(“**白箱**”复用
- **组合/聚合复用**:耦合度相对较低,有选择性地调用成员对象的操作;可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。(“**黑箱**”复用
> 合成复用原则实例
- 实例说明
某软件公司开发人员在初期的CRM系统设计中考虑到客户数量不多系统采用Access作为数据库与数据库操作有关的类例如CustomerDAO类等都需要连接数据库连接数据库的方法getConnection()封装在DBUtil类中由于需要重用DBUtil类的getConnection()方法设计人员将CustomerDAO作为DBUtil类的子类初始设计方案结构如图所示。
![image-20211201154924594](设计模式/image-20211201154924594.png)
随着客户数量的增加系统决定升级为Oracle数据库因此需要增加一个新的OracleDBUtil类来连接Oracle数据库由于在初始设计方案中CustomerDAO和DBUtil之间是继承关系因此在更换数据库连接方式时需要修改CustomerDAO类的源代码将CustomerDAO作为OracleDBUtil的子类这将违背开闭原则。当然也可以直接修改DBUtil类的源代码这同样也违背了开闭原则。
- 实例解析
![image-20211201154958526](设计模式/image-20211201154958526.png)
## 迪米特法则
> 迪米特法则定义
**迪米特法则**:每一个软件单位对其他的单位都只有**最少的知识**,而且局限于那些与本单位密切相关的软件单位。
**Law of Demeter (LoD)**: Each unit should have **only limited knowledge** about other units: only units "closely" related to the current unit.
> 迪米特法则分析
- 迪米特法则来自于1987年美国东北大学(Northeastern University)一个名为“Demeter”的研究项目
- 迪米特法则要求**一个软件实体应当尽可能少地与其他实体发生相互作用**
- 应用迪米特法则**可降低系统的耦合度**,使类与类之间保持松散的耦合关系
- 迪米特法则要求在设计系统时,应该**尽量减少对象之间的交互**
- 如果两个对象之间不必彼此直接通信,那么这两个对象就**不应该发生任何直接的相互作用**
- 如果其中一个对象需要调用另一个对象的方法,可以通过**“第三者”转发**这个调用
- 通过**引入一个合理的“第三者”来降低现有对象之间的耦合度**
> 迪米特法则实例
- 实例说明
![image-20211201155515771](设计模式/image-20211201155515771.png)
某软件公司所开发CRM系统包含很多业务操作窗口在这些窗口中某些界面控件之间存在复杂的交互关系一个控件事件的触发将导致多个其他界面控件产生响应。例如当一个按钮(Button)被单击时,对应的列表框(List)、组合框(ComboBox)、文本框(TextBox)、文本标签(Label)等都将发生改变,在初始设计方案中,界面控件之间的交互关系可以简化为如图所示的结构。
![image-20211201155454712](设计模式/image-20211201155454712.png)
由于界面控件之间的交互关系复杂,导致在该窗口中增加新的界面控件时需要修改与之交互的其他控件的源代码,系统扩展性较差,也不便于增加和删除控件。
现使用迪米特法则对其进行重构。
- 实例解析
![image-20211201155535436](设计模式/image-20211201155535436.png)

@ -0,0 +1,290 @@
## 创建型模式概述
- **创建型模式(Creational Pattern)关注对象的创建过程**
- 创建型模式**对类的实例化过程进行了抽象**,能够**将软件模块中对象的创建和对象的使用分离**,对用户**隐藏了类的实例的创建细节**
- 创建型模式**描述如何将对象的创建和使用分离**,让用户在使用对象时无须关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展
- 创建型模式关注点
- **创建什么(What)**
- **由谁创建(Who)**
- **何时创建(When)**
> 创建模式一览表
| **模式名称** | **定** **义** | **学习难度** | **使用频率** |
| ------------------------------------------ | ------------------------------------------------------------ | ------------ | ------------ |
| **简单工厂模式(Simple Factory Pattern)** | **定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。** | ★★☆☆☆ | ★★★☆☆ |
| **工厂方法模式(Factory Method Pattern)** | **定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。** | ★★☆☆☆ | ★★★★★ |
| **抽象工厂模式(Abstract Factory Pattern)** | **提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。** | ★★★★☆ | ★★★★★ |
| **建造者模式(Builder Pattern)** | **将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。** | ★★★★☆ | ★★☆☆☆ |
| **原型模式(Prototype Pattern)** | **使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。** | ★★★☆☆ | ★★★☆☆ |
| **单例模式(Singleton Pattern)** | **确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。** | ★☆☆☆☆ | ★★★★☆ |
## 简单工厂模式概述
![image-20211201155957270](设计模式/image-20211201155957270.png)
> 简单工厂模式基本实现流程
- **具体产品类**:将需要创建的各种不同产品对象的相关代码封装到具体产品类中
- **抽象产品类**:将具体产品类公共的代码进行抽象和提取后封装在一个抽象产品类中
- **工厂类**:提供一个工厂类用于创建各种产品,在工厂类中提供一个创建产品的工厂方法,该方法可以根据所传入参数的不同创建不同的具体产品对象
- **客户端**:只需调用工厂类的工厂方法并传入相应的参数即可得到一个产品对象
```java
if(arg.equalsIgnoreCase("Apple")) {
return new Apple();
}
else if(arg.equalsIgnoreCase("Banana")) {
return new Banana();
}
else {
......
}
```
> 简单工厂模式的定义
**简单工厂模式 (Simple Factory Pattern)**:定义一个工厂类,它可以**根据参数的不同返回不同类的实例**,被创建的实例通常都**具有共同的父类。**
- **类创建型**模式
- **使用程度**:中等
- 在简单工厂模式中用于创建实例的方法通常是**静态(static)方法**,因此又被称为**静态工厂方法(Static Factory Method)模式**
- **要点**:如果需要什么,**只需要传入一个正确的参数,就可以获取所需要的对象**,而**无须知道其创建细节**
## 简单工厂模式的结构与实现
> 简单工厂模式的结构
![image-20211204094718737](设计模式/image-20211204094718737.png)
> 简单工厂模式包含以下三个角色
- Factory工厂角色
- Product抽象产品角色
- ConcreteProduct具体产品角色
> 简单工厂模式的实现
- 典型的**抽象产品类**代码
```java
public abstract class Product {
//所有产品类的公共业务方法
public void methodSame() {
//公共方法的实现
}
 
//声明抽象业务方法
public abstract void methodDiff();
}
```
- 典型的**具体产品类**代码:
```java
public class ConcreteProduct extends Product{
//实现业务方法
public void methodDiff() {
//业务方法的实现
}
}
```
- 典型的**工厂类**代码:
```java
public class Factory {
//静态工厂方法
public static Product getProduct(String arg) {
Product product = null;
if (arg.equalsIgnoreCase("A")) {
product = new ConcreteProductA();
//初始化设置product
}
else if (arg.equalsIgnoreCase("B")) {
product = new ConcreteProductB();
//初始化设置product
}
return product;
}
}
```
- 典型的**客户端**代码:
```java
public class Client {
public static void main(String args[]) {
Product product;
product = Factory.getProduct("A"); //通过工厂类创建产品对象
product.methodSame();
product.methodDiff();
}
}
```
## 简单工厂模式的应用实例
> 实例说明
![image-20211204095224651](设计模式/image-20211204095224651.png)
某软件公司要基于Java语言开发一套图表库该图表库可以为应用系统提供多种不同外观的图表例如柱状图(HistogramChart)、饼状图(PieChart)、折线图(LineChart)等。该软件公司图表库设计人员希望为应用系统开发人员提供一套灵活易用的图表库,通过设置不同的参数即可得到不同类型的图表,而且可以较为方便地对图表库进行扩展,以便能够在将来增加一些新类型的图表。
现使用简单工厂模式来设计该图表库。
> 实例类图
![image-20211204095240254](设计模式/image-20211204095240254.png)
> 实例代码
- Chart抽象图表接口充当抽象产品类
- HistogramChart柱状图类充当具体产品类
- PieChart饼状图类充当具体产品类
- LineChart折线图类充当具体产品类
- ChartFactory图表工厂类充当工厂类
- Client客户端测试类
```java
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
 
public class XMLUtil {
//该方法用于从XML配置文件中提取图表类型并返回类型名
public static String getChartType() {
try {
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src//designpatterns//simplefactory//config.xml"));
//获取包含图表类型的文本结点
NodeList nl = doc.getElementsByTagName("chartType");
Node classNode = nl.item(0).getFirstChild();
String chartType = classNode.getNodeValue().trim();
return chartType;
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
```
> 结果与分析
- 通过配置文件读取
```java
Chart chart;
String type = XMLUtil.getChartType(); //读取配置文件中的参数
chart = ChartFactory.getChart(type); //创建产品对象
```
- 通过静态工厂方法读取
```java
Chart chart;
chart = ChartFactory.getChart(“histogram”); //通过静态工厂方法创建对象
```
## 创建对象与使用对象
> Java语言创建对象的几种方式
- 使用**new关键字**直接创建对象
- 通过**反射机制**创建对象
- 通过**克隆方法**创建对象
- 通过**工厂类**创建对象
> 实例分析
- 使用**new关键字**创建对象
若改为HibernateUserDAO必须修改源代码违背开闭原则
```java
public class LoginAction {
private UserDAO udao;
public LoginAction() {
udao = new JDBCUserDAO(); //创建对象
}
public String execute() {
//其他代码
udao.findUserById(); //使用对象
//其他代码
}
}
```
- 引入工厂类UserDaoFactory
- 如果UserDAO的某个子类的构造函数发生改变或者需要添加或移除不同的子类**只要维护UserDAOFactory的代码不会影响到LoginAction**
- 如果UserDAO的接口发生改变例如添加、移除方法或改变方法名**只需要修改LoginAction不会给UserDAOFactory带来任何影响**
两个类A和B之间的关系应该**仅仅是A创建B或者是A使用B而不能两种关系都有。**将对象的创建和使用分离,使得系统更加符合单一职责原则,有利于对功能的复用和系统的维护。
![image-20211204100115866](设计模式/image-20211204100115866.png)
> 将对象的创建与使用分离的其他好处
- **防止用来实例化一个类的数据和代码在多个类中到处都是**,可以将有关创建的知识搬移到一个工厂类中,**解决代码重复、创建蔓延的问题**[Move Creation Knowledge to Factory. JoshuaKerievsky, Refactoring to Patterns, 2004]
- 构造函数的名字都与类名相同,从构造函数和参数列表中大家很难了解不同构造函数所构造的产品的差异—>将对象的创建过程封装在工厂类中,可以**提供一系列名字完全不同的工厂方法,每一个工厂方法对应一个构造函数**,客户端可以以一种更加可读、易懂的方式来创建对象
![image-20211204100610307](设计模式/image-20211204100610307.png)
> 何时不需要工厂?
- **无须为系统中的每一个类都配备一个工厂类**
- 如果一个类很简单,而且**不存在太多变化,其构造过程也很简单**,此时就无须为其提供工厂类,直接在使用之前实例化即可
- 否则会导致工厂泛滥,增加系统的复杂度
- **例如java.lang.String**
## 简单工厂模式的简化
> 将**抽象产品类和工厂类合并**,将**静态工厂方法移至抽象产品类**中
![image-20211204103959038](设计模式/image-20211204103959038.png)
## 简单工厂模式的优缺点与适用环境
> 模式优点
- 实现了**对象创建和使用的分离**
- 客户端**无须知道所创建的具体产品类的类名**,只需要知道具体产品类所对应的参数即可
- 通过引入配置文件,**可以在不修改任何客户端代码的情况下更换和增加新的具体产品类**,在一定程度上提高了系统的灵活性
> 模式缺点
- **工厂类**集中了所有产品的创建逻辑,**职责过重**,一旦不能正常工作,整个系统都要受到影响
- **增加系统中类的个数**(引入了新的工厂类),增加了系统的复杂度和理解难度
- **系统扩展困难**,一旦添加新产品不得不修改工厂逻辑
- 由于使用了静态工厂方法,造成**工厂角色无法形成基于继承的等级结构**,工厂类不能得到很好地扩展
> 模式适用环境
- 工厂类**负责创建的对象比较少**,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂
- **客户端只知道传入工厂类的参数,对于如何创建对象并不关心**

@ -0,0 +1,303 @@
## 工厂方法模式概述
> 使用**简单工厂方法模式**设计的按钮工厂
![image-20211205111547616](设计模式/image-20211205111547616.png)
> 使用**工厂方法模式**改进的按钮工厂
![image-20211205111657540](设计模式/image-20211205111657540.png)
> 分析
- **工厂方法模式:**
- 不再提供一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成
- 如果出现新的按钮类型,只需要为这种新类型的按钮定义一个具体的工厂类就可以创建该新按钮的实例
- 符合开闭原则
> 工厂方法模式的定义
**工厂方法模式:**定义一个用于创建对象的接口,但是**让子类决定将哪一个类实例化**。工厂方法模式让一个类的实例化**延迟到其子类**。
**Factory Method Pattern:** Define an interface for creating an object, but **let subclasses decide which class to instantiate**. Factory Method lets a class **defer instantiation to subclasses**.
- **类创建型**模式
- 简称为**工厂模式(Factory Pattern)**
- 又可称作**虚拟构造器模式(Virtual Constructor Pattern)**或**多态工厂模式(Polymorphic Factory Pattern)**
- **工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象**
- 目的是**将产品类的实例化操作延迟到工厂子类中完成**,即**通过工厂子类来确定究竟应该实例化哪一个具体产品类**
## 工厂方法模式的结构与实现
> 工厂方法模式的结构
![image-20211205112123440](设计模式/image-20211205112123440.png)
> 工厂方法模式包含以下4个角色
- Product抽象产品
- ConcreteProduct具体产品
- Factory抽象工厂
- ConcreteFactory具体工厂
> 工厂方法模式的实现
- 典型的**抽象工厂类**代码:
```java
public interface Factory {
public Product factoryMethod();
}
```
- 典型的**具体工厂类**代码:
```java
public class ConcreteFactory implements Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
```
- 典型的**客户端**代码片段:
```java
// ……
Factory factory;
factory = new ConcreteFactory(); //可通过配置文件和反射机制实现
Product product;
product = factory.factoryMethod();
// ……
```
## 工厂方法模式的应用实例
> 实例说明
某系统运行日志记录器(Logger)可以通过多种途径保存系统的运行日志,例如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。在设计各类日志记录器时,开发人员发现需要对日志记录器进行一些初始化工作,初始化参数的设置过程较为复杂,而且某些参数的设置有严格的先后次序,否则可能会发生记录失败。
为了更好地封装记录器的初始化过程并保证多种记录器切换的灵活性,现使用工厂方法模式设计该系统。
> 实例类图
![image-20211205112552629](设计模式/image-20211205112552629.png)
> 实例代码
- Logger日志记录器接口充当抽象产品角色
- DatabaseLogger数据库日志记录器充当具体产品角色
- FileLogger文件日志记录器充当具体产品角色
- LoggerFactory日志记录器工厂接口充当抽象工厂角色
- DatabaseLoggerFactory数据库日志记录器工厂类充当具体工厂角色
- FileLoggerFactory文件日志记录器工厂类充当具体工厂角色
- Client客户端测试类
> 结果与分析
**在未使用配置文件和反射机制之前,更换具体工厂类需修改客户端源代码,但无须修改类库代码**
## 反射机制与配置文件
> Java反射机制(Java Reflection)
- Java反射(Java Reflection)是指在**程序运行时获取已知名称的类或已有对象的相关信息的一种机制**,包括类的方法、属性、父类等信息,还包括**实例的创建和实例类型的判断**等
- Class类的实例表示正在运行的Java应用程序中的类和接口其**forName(String className)方法**可以返回与带有给定字符串名的类或接口相关联的 Class对象**再通过Class对象的newInstance()方法创建此对象所表示的类的一个新实例**,即通过一个类名字符串得到类的实例
```java
//通过类名生成实例对象并将其返回
Class c=Class.forName(“java.lang.String");
Object obj=c.newInstance();
return obj;
```
> 配置文件
- **纯文本**文件例如XML文件properties文件yml文件……等
- 通常是XML文件可以将类名存储在配置文件中例如具体工厂类的类名
```xml
<!— config.xml -->
<?xml version="1.0"?>
<config>
<className>designpatterns.factorymethod.FileLoggerFactory</className>
</config>
```
> 修改后的代码
- 客户端代码
```java
package designpatterns.factorymethod;
public class Client {
public static void main(String args[]) {
LoggerFactory factory;
Logger logger;
factory = (LoggerFactory)XMLUtil.getBean(); //getBean()的返回类型为Object需要进行强制类型转换
logger = factory.createLogger();
logger.writeLog();
}
}
```
- 工具类XMLUtil
```java
package designpatterns.factorymethod;
//XMLUtil.java
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;
public class XMLUtil {
//该方法用于从XML配置文件中提取具体类类名并返回一个实例对象
public static Object getBean() {
try {
//创建DOM文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src//designpatterns//factorymethod//config.xml"));
//获取包含类名的文本结点
NodeList nl = doc.getElementsByTagName("className");
Node classNode=nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//通过类名生成实例对象并将其返回
Class c=Class.forName(cName);
Object obj=c.newInstance();
return obj;
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
```
> 增加新产品的步骤
- 增加一个新的**具体产品类**作为抽象产品类的子类
- 增加一个新的**具体工厂类**作为抽象工厂类的子类,该工厂用于创建新增的具体产品对象
- 修改**配置文件**,用新的具体工厂类的类名字符串替换原有工厂类类名字符串
- 编译新增具体产品类和具体工厂类,运行客户端代码,即可完成新产品的增加和使用
## 工厂方法的重载
> 结构图
![image-20211205113839484](设计模式/image-20211205113839484.png)
> 示意代码
- 抽象工厂类LoggerFactory
```java
public interface LoggerFactory {
public Logger createLogger();
public Logger createLogger(String args);
public Logger createLogger(Object obj);
}
```
- 具体工厂类DatabaseLoggerFactory
```java
public class DatabaseLoggerFactory implements LoggerFactory {
public Logger createLogger() {
//使用默认方式连接数据库,代码省略
Logger logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
return logger;
}
public Logger createLogger(String args) {
//使用参数args作为连接字符串来连接数据库代码省略
Logger logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
return logger;
}
public Logger createLogger(Object obj) {
//使用封装在参数obj中的连接字符串来连接数据库代码省略
Logger logger = new DatabaseLogger();
//使用封装在参数obj中的数据来初始化数据库日志记录器代码省略
return logger;
}
}
//其他具体工厂类代码省略
```
## 工厂方法的隐藏
- **目的**:为了进一步简化客户端的使用
- **实现**:在工厂类中直接调用产品类的业务方法,客户端无须调用工厂方法创建产品对象,**直接使用工厂对象即可调用所创建的产品对象中的业务方法**
![image-20211205114058379](设计模式/image-20211205114058379.png)
- 抽象工厂类LoggerFactory示意代码
```java
//将接口改为抽象类
public abstract class LoggerFactory {
//在工厂类中直接调用日志记录器类的业务方法writeLog()
public void writeLog() {
Logger logger = this.createLogger();
logger.writeLog();
}
public abstract Logger createLogger();
}
```
- 客户端代码:
```java
public class Client {
public static void main(String args[]) {
LoggerFactory factory;
factory = (LoggerFactory)XMLUtil.getBean();
factory.writeLog(); //直接使用工厂对象来调用产品对象的业务方法
}
}
```
## 工厂方法模式的优缺点与适用环境
> 模式优点
- 工厂方法用来创建客户所需要的产品,同时还**向客户隐藏了哪种具体产品类将被实例化这一细节**
- 能够**让工厂自主确定创建何种产品对象**,而如何创建这个对象的细节则完全封装在具体工厂内部
- 在系统中加入新产品时,**完全符合开闭原则**
> 模式缺点
- 系统中**类的个数将成对增加**,在一定程度上增加了系统的复杂度,会给系统带来一些额外的开销
- **增加了系统的抽象性和理解难度**
> 模式适用环境
- **客户端不知道它所需要的对象的类**(客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体产品对象由具体工厂类创建)**
- **抽象工厂类通过其子类来指定创建哪个对象**

@ -0,0 +1,155 @@
## 产品等级结构与产品族
> 工厂方法模式
- 每个具体工厂**只有一个或者一组重载的工厂方法,只能生产一种产品**,可能会导致系统中存在大量的工厂类,势必会增加系统的开销
> 抽象工厂模式
- 一个工厂**可以生产一系列产品(一族产品)**,极大减少了工厂类的数量
> 概念
- **产品等级结构:**产品等级结构即产品的**继承结构**
- **产品族:**产品族是指由**同一个工厂生产的,位于不同产品等级结构中的一组产品**
![image-20211205115157379](设计模式/image-20211205115157379.png)
## 抽象工厂模式概述
> 模式动机
- 当系统所提供的工厂生产的具体产品并不是一个简单的对象,而是**多个位于不同产品等级结构、属于不同类型的具体产品时就可以使用抽象工厂模式**
- 抽象工厂模式是**所有形式的工厂模式中最为抽象和最具一般性的一种形式**
![image-20211205115321822](设计模式/image-20211205115321822.png)
> 抽象工厂模式的定义
**抽象工厂模式:**提供一个**创建一系列相关或相互依赖对象的接口**,而无须指定它们具体的类。
**Abstract Factory Pattern:** Provide an interface for **creating families of related or dependent objects** without specifying their concrete classes.
- 对象创建型模式
- 又称为**工具(Kit)**模式
- 抽象工厂模式中的具体工厂不只是创建一种产品,它**负责创建一族产品****
- 当**一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、更有效率**
## 抽象工厂模式的结构与实现
> 抽象工厂模式的结构
![image-20211205115536684](设计模式/image-20211205115536684.png)
> 抽象工厂模式包含以下4个角色
- AbstractFactory抽象工厂
- ConcreteFactory具体工厂
- AbstractProduct抽象产品
- ConcreteProduct具体产品
> 抽象工厂模式的实现
- 典型的**抽象工厂类**代码:
```java
public interface AbstractFactory {
public AbstractProductA createProductA(); //工厂方法一
public AbstractProductB createProductB(); //工厂方法二
……
}
```
- 典型的**具体工厂类**代码:
```java
public class ConcreteFactory1 implements AbstractFactory {
//工厂方法一
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
//工厂方法二
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
……
}
```
## 抽象工厂模式的应用实例
> 实例说明
某软件公司要开发一套界面皮肤库可以对基于Java的桌面软件进行界面美化。用户在使用时可以通过菜单来选择皮肤不同的皮肤将提供视觉效果不同的按钮、文本框、组合框等界面元素例如春天(Spring)风格的皮肤将提供浅绿色的按钮、绿色边框的文本框和绿色边框的组合框,而夏天(Summer)风格的皮肤则提供浅蓝色的按钮、蓝色边框的文本框和蓝色边框的组合框,其结构示意图如下图所示:
![image-20211205115847988](设计模式/image-20211205115847988.png)
> 实例类图
![image-20211205115936829](设计模式/image-20211205115936829.png)
> 实例代码
- Button按钮接口充当抽象产品
- SpringButtonSpring按钮类充当具体产品
- SummerButtonSummer按钮类充当具体产品
- TextField文本框接口充当抽象产品
- SpringTextFieldSpring文本框类充当具体产品
- SummerTextFieldSummer文本框类充当具体产品
- ComboBox组合框接口充当抽象产品
- SpringComboBoxSpring组合框类充当具体产品
- SummerComboBoxSummer组合框类充当具体产品
- SkinFactory界面皮肤工厂接口充当抽象工厂
- SpringSkinFactorySpring皮肤工厂充当具体工厂
- SummerSkinFactorySummer皮肤工厂充当具体工厂
- Client客户端测试类
> 结果与分析
- 更换皮肤,只需要修改配置文件
```xml
<?xml version="1.0"?>
<config>
<className>designpatterns.abstractfactory.SpringSkinFactory
</className>
</config>
```
## 开闭原则的倾斜性
> 增加产品族
- **对于增加新的产品族**,抽象工厂模式很好地**支持**了**开闭原则**,只需要增加具体产品并对应增加一个新的具体工厂,对已有代码无须做任何修改
> 增加新的产品等级结构
- **对于增加新的产品等级结构**,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法,**违背了开闭原则**
## 抽象工厂模式的优缺点与适用环境
> 模式优点
- **隔离了具体类的生成**,使得客户端并不需要知道什么被创建**
- 当一个产品族中的多个对象被设计成一起工作时,它**能够保证客户端始终只使用同一个产品族中的对象**
- **增加新的产品族很方便**,无须修改已有系统,**符合开闭原则**
> 模式缺点
- **增加新的产品等级结构麻烦**,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,**违背了开闭原则**
> 模式适用环境
- 一个系统**不应当依赖于产品类实例如何被创建、组合和表达的细节**
- 系统中有多于一个的产品族,但**每次只使用其中某一产品族**
- **属于同一个产品族的产品将在一起使用**,这一约束必须在系统的设计中体现出来
- **产品等级结构稳定**,在设计完成之后不会向系统中增加新的产品等级结构或者删除已有的产品等级结构

@ -0,0 +1,296 @@
## 建造者模式概述
> 复杂对象
![image-20211205122016118](设计模式/image-20211205122016118.png)
> 分析
如何将这些部件**组装成一辆完整的汽车**并返回给用户?
**建造者模式**:建造者模式可以将部件本身和它们的组装过程分开,关注如何一步步创建一个包含多个组成部分的复杂对象,用户只需要指定复杂对象的类型即可得到该对象,而无须知道其内部的具体构造细节。
> 建造者模式的定义
**建造者模式:**将一个**复杂对象的构建与它的表示分离**,使得同样的构建过程可以创建不同的表示。
**Builder Pattern**: **Separate the construction of a complex object from its representation** so that the same construction process can create different representations.
- **对象创建型**模式
- 将客户端与包含多个部件的复杂对象的创建过程分离,**客户端无须知道复杂对象的内部组成部分与装配方式**,只需要知道所需建造者的类型即可
- **关注如何逐步创建一个复杂的对象**,不同的建造者定义了不同的创建过程
## 建造者模式的结构与实现
> 建造者模式的结构
![image-20211205122455892](设计模式/image-20211205122455892.png)
> 建造者模式包含以下4个角色
- Builder抽象建造者
- ConcreteBuilder具体建造者
- Product产品
- Director指挥者
> 建造者模式的实现
- 典型的**复杂对象类**代码:
```java
public class Product {
private String partA; //定义部件,部件可以是任意类型,包括值类型和引用类型
private String partB;
private String partC;
//partA的Getter方法和Setter方法省略
//partB的Getter方法和Setter方法省略
//partC的Getter方法和Setter方法省略
}
```
- 典型的**抽象建造者类**代码:
```java
public abstract class Builder {
//创建产品对象
protected Product product=new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回产品对象
public Product getResult() {
return product;
}
}
```
- 典型的**具体建造者类**代码:
```java
public class ConcreteBuilder1 extends Builder{
public void buildPartA() {
product.setPartA("A1");
}
public void buildPartB() {
product.setPartB("B1");
}
public void buildPartC() {
product.setPartC("C1");
}
}
```
- 典型的**指挥者**类代码:
```java
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder=builder;
}
public void setBuilder(Builder builder) {
this.builder=builer;
}
//产品构建与组装方法
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
```
- **客户类**代码片段:
```java
// ……
Builder builder = new ConcreteBuilder1(); //可通过配置文件实现
Director director = new Director(builder);
Product product = director.construct();
// ……
```
## 建造者模式的应用实例
> 实例说明
某游戏软件公司决定开发一款基于角色扮演的多人在线网络游戏,玩家可以在游戏中扮演虚拟世界中的一个特定角色,角色根据不同的游戏情节和统计数据(例如力量、魔法、技能等)具有不同的能力,角色也会随着不断升级而拥有更加强大的能力。
作为该游戏的一个重要组成部分,需要对游戏角色进行设计,而且随着该游戏的升级将不断增加新的角色。通过分析发现,游戏角色是一个复杂对象,它包含性别、面容等多个组成部分,不同类型的游戏角色,其性别、面容、服装、发型等外部特性都有所差异,例如“天使”拥有美丽的面容和披肩的长发,并身穿一袭白裙;而“恶魔”极其丑陋,留着光头并穿一件刺眼的黑衣。
无论是何种造型的游戏角色,它的创建步骤都大同小异,都需要逐步创建其组成部分,再将各组成部分装配成一个完整的游戏角色。现使用建造者模式来实现游戏角色的创建。
> 实例类图
![image-20211205123105101](设计模式/image-20211205123105101.png)
> 实例代码
- Actor游戏角色类充当复杂产品对象
- ActorBuilder游戏角色建造者充当抽象建造者
- HeroBuilder英雄角色建造者充当具体建造者
- AngelBuilder天使角色建造者充当具体建造者
- DevilBuilder恶魔角色建造者充当具体建造者
- ActorController角色控制器充当指挥者
- Client客户端测试类
> 结果及分析
- 如果需要更换具体角色建造者,只需要修改**配置文件**
- 当需要增加新的具体角色建造者时,只需将新增具体角色建造者作为抽象角色建造者的子类,然后修改配置文件即可,原有代码无须修改,**完全符合开闭原则**
```xml
<?xml version="1.0"?>
<config>
<className>designpatterns.builder.AngelBuilder</className>
</config>
```
## 指挥者类的深入讨论
> 省略Director
- 将Director和抽象建造者Builder合并
```java
public abstract class ActorBuilder {
protected static Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
 
public static Actor construct(ActorBuilder ab) {
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
ab.buildHairstyle();
return actor;
}
}
```
```java
// ……
ActorBuilder ab;
ab = (ActorBuilder)XMLUtil.getBean();
Actor actor;
actor = ActorBuilder.construct(ab);
// ……
```
- 将construct()方法中的参数去掉直接在construct()方法中调用buildPartX()方法
```java
public abstract class ActorBuilder {
protected Actor actor = new Actor();
public abstract void buildType();
public abstract void buildSex();
public abstract void buildFace();
public abstract void buildCostume();
public abstract void buildHairstyle();
public Actor construct() {
this.buildType();
this.buildSex();
this.buildFace();
this.buildCostume();
this.buildHairstyle();
return actor;
}
}
```
```java
// ……
ActorBuilder ab;
ab = (ActorBuilder)XMLUtil.getBean();
Actor actor;
actor = ab.construct();
// ……
```
> 钩子方法的引入
```java
public class DevilBuilder extends ActorBuilder {
public void buildType() {
actor.setType("恶魔");
}
public void buildSex() {
actor.setSex("妖");
}
public void buildFace() {
actor.setFace("丑陋");
}
public void buildCostume() {
actor.setCostume("黑衣");
}
public void buildHairstyle() {
actor.setHairstyle("光头");
}
//覆盖钩子方法
public boolean isBareheaded() {
return true;
}
}
```
```java
public class ActorController {
public Actor construct(ActorBuilder ab) {
Actor actor;
ab.buildType();
ab.buildSex();
ab.buildFace();
ab.buildCostume();
//通过钩子方法来控制产品的构建
if(!ab.isBareheaded()) {
ab.buildHairstyle();
}
actor=ab.createActor();
return actor;
}
}
```
## 建造者模式的优缺点与适用环境
> 模式优点
- 客户端不必知道产品内部组成的细节,**将产品本身与产品的创建过程解耦**,使得**相同的创建过程可以创建不同的产品对象**
- 每一个具体建造者都相对独立,与其他的具体建造者无关,因此**可以很方便地替换具体建造者或增加新的具体建造者**,扩展方便,**符合开闭原则**
- 可以**更加精细地控制产品的创建过程**
> 模式缺点
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,**如果产品之间的差异性很大,不适合使用建造者模式**,因此其**使用范围受到一定的限制**
- 如果**产品的内部变化复杂**,可能会**需要定义很多具体建造者类**来实现这种变化,导致系统变得很庞大,增加了系统的理解难度和运行成本
> 模式适用环境
- **需要生成的产品对象有复杂的内部结构**,这些产品对象通常包含多个成员变量
- **需要生成的产品对象的属性相互依赖**,需要指定其生成顺序
- **对象的创建过程独立于创建该对象的类**。在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中
- **隔离复杂对象的创建和使用**,并使得相同的创建过程可以创建不同的产品

@ -0,0 +1,210 @@
## 原型模式概述
- 孙悟空“拔毛变小猴”
![image-20211205124330365](设计模式/image-20211205124330365.png)
> 分析
- 孙悟空:根据自己的形状**复制(克隆)**出多个身外身
- 软件开发:通过复制一个**原型对象**得到多个与原型对象一模一样的新对象
> 原型模式的定义
**原型模式:**使用原型实例指定待创建对象的类型,并且**通过复制这个原型来创建新的对象**。
**Prototype Pattern:** Specify the kinds of objects to create using a prototypical instance, and **create new objects by copying this prototype**.
- **对象创建型**模式
- **工作原理**:将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象**通过请求原型对象复制自己来实现创建过程**
- 创建新对象(也称为克隆对象)的**工厂**就是**原型类**自身,**工厂方法**由负责复制原型对象的**克隆方法**来实现
- 通过克隆方法所创建的对象是**全新的对象**,它们在内存中拥有新的地址,每一个克隆对象都是**独立**的
- 通过不同的方式对克隆对象进行修改以后,**可以得到一系列相似但不完全相同的对象**
## 原型模式的结构与实现
> 原型模式的结构
![image-20211205124638573](设计模式/image-20211205124638573.png)
> 原型模式包含以下3个角色
- Prototype抽象原型类
- ConcretePrototype具体原型类
- Client客户类
> 浅克隆与深克隆
- **浅克隆(Shallow Clone)**:当原型对象被复制时,**只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制**
- **深克隆(Deep Clone)**:除了对象本身被复制外,**对象所包含的所有成员变量也将被复制**
![image-20211205124816903](设计模式/image-20211205124816903.png)
> 原型模式的实现
- 通用的克隆实现方法
```java
public interface Prototype {
public Prototype clone();
}
public class ConcretePrototype implements Prototype {
private String attr;
public void setAttr(String attr) {
this.attr = attr;
}
public String getAttr() {
return this.attr;
}
//克隆方法
public Prototype clone() {
Prototype prototype = new ConcretePrototype(); //创建新对象
prototype.setAttr(this.attr);
return prototype;
}
}
```
```java
// ……
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAttr("Sunny");
ConcretePrototype copy = (ConcretePrototype)prototype.clone();
// ……
```
- Java语言中的clone()方法和Cloneable接口
- 在Java语言中提供了一个**clone()方法**用于实现**浅克隆**该方法使用起来很方便直接调用super.clone()方法即可实现克隆
```java
public class ConcretePrototype implements Cloneable {
// .....
//Shallow Clone
public Prototype clone( {
Object object = null;
try {
object = super.clone();
catch (CloneNotSupportedException exception) {
System.errprintn("Not support cloneable");
return (Prototype)object;
// .....
}
```
```java
Prototype protptype = new ConcretePrototype();
Prototype copy = protptype.clone();
```
## 原型模式的应用实例
> 实例说明
在使用某OA系统时有些岗位的员工发现他们每周的工作都大同小异因此在填写工作周报时很多内容都是重复的为了提高工作周报的创建效率大家迫切希望有一种机制能够快速创建相同或者相似的周报包括创建周报的附件。
试使用原型模式对该OA系统中的工作周报创建模块进行改进。
> 实例类图
![image-20211205125337694](设计模式/image-20211205125337694.png)
> 实例代码
- Object**抽象原型角色**
```java
package java.lang;
public class Object {
// ……
protected native Object clone() throws CloneNotSupportedException;
// ……
}
```
> 结果与分析
周报是否相同? **false**
附件是否相同? **true**
- 工作周报对象被成功复制,但是附件对象并没有复制,实现了**浅克隆**
> 深克隆解决方案
- 工作周报类WeeklyLog和附件类Attachment实现**Serializable**接口
![image-20211205125613038](设计模式/image-20211205125613038.png)
- 修改工作周报类WeeklyLog的clone()方法
- (1) **WeeklyLog**: 具体原型类
- (2) **Attachment**: 具体原型类
- (3) Client原型管理器
周报是否相同? **false**
附件是否相同? **false**
- 工作周报对象和附件对象都成功复制,实现了**深克隆**
## 原型管理器
> 定义
- **原型管理器(Prototype Manager)**将多个原型对象存储在一个集合中供客户端使用,它是一个**专门负责克隆对象的工厂**,其中**定义了一个集合用于存储原型对象**,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得
> 结构
![image-20211205125905052](设计模式/image-20211205125905052.png)
> 实现
```java
import java.util.*;
public class PrototypeManager {
private Hashtable prototypeTable=new Hashtable(); //使用Hashtable存储原型对象
public PrototypeManager() {
prototypeTable.put("A", new ConcretePrototypeA());
prototypeTable.put("B", new ConcretePrototypeB());
}
public void add(String key, Prototype prototype) {
prototypeTable.put(key,prototype);
}
public Prototype get(String key) {
Prototype clone = null;
clone = ((Prototype) prototypeTable.get(key)).clone(); //通过克隆方法创建新对象
return clone;
}
}
```
## 原型模式的优缺点与适用环境
> 模式优点
- **简化对象的创建过程**,通过复制一个已有实例可以**提高新实例的创建效率**
- **扩展性较好**
- 提供了**简化的创建结构**,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品
- 可以使用深克隆的方式**保存对象的状态**,以便在需要的时候使用,可辅助实现撤销操作
> 模式缺点
- **需要为每一个类配备一个克隆方法**,而且该克隆方法位于一个类的内部,当**对已有的类进行改造时,需要修改源代码,违背了开闭原则**
- 在**实现深克隆时需要编写较为复杂的代码**,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦
> 模式适用环境
- **创建新对象成本较大**,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
- 系统要保存对象的状态,而**对象的状态变化很小**
- 需要避免使用分层次的工厂类来创建分层次的对象
- **Ctrl + C —> Ctrl + V**

@ -0,0 +1,339 @@
## 单例模式概述
![image-20211205130348309](设计模式/image-20211205130348309.png)
> 分析
如何保证一个类只有一个实例并且这个实例易于被访问?
1. **全局变量**:可以确保对象随时都可以被访问,但**不能防止创建多个对象**
2. **让类自身负责创建和保存它的唯一实例**,并保证不能创建其他实例,它还提供一个访问该实例的方法
> 单例模式的定义
**单例模式:**确保一个类**只有一个实例**,并提供一个**全局访问点**来访问这个唯一实例。
**Singleton Pattern:** Ensure a class has **only one instance**, and provide **a global point** of access to it.
- **对象创建型**模式
- 要点:
- 某个类**只能有一个实例**
- 必须**自行创建这个实例**
- 必须**自行向整个系统提供这个实例**
## 单例模式的结构与实现
> 单例模式的结构
![image-20211205130705851](设计模式/image-20211205130705851.png)
> 单例模式只包含一个单例角色:
- Singleton单例
> 单例模式的结构与实现
- 私有构造函数
- 静态私有成员变量(自身类型)
- 静态公有的工厂方法
```java
public class Singleton {
private static Singleton instance=null; //静态私有成员变量
//私有构造函数
private Singleton() {
}
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance() {
if(instance==null)
instance=new Singleton();
return instance;
}
}
```
## 单例模式的应用实例
> 实例说明
某软件公司承接了一个服务器负载均衡(Load Balance)软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量分发到服务器集群中的多台设备上进行并发处理,提高了系统的整体处理能力,缩短了响应时间。由于集群中的服务器需要动态删减,且客户端请求需要统一分发,因此需要确保负载均衡器的唯一性,只能有一个负载均衡器来负责服务器的管理和请求的分发,否则将会带来服务器状态的不一致以及请求分配冲突等问题。如何确保负载均衡器的唯一性是该软件成功的关键,试使用单例模式设计服务器负载均衡器。
> 实例类图
![image-20211205131022760](设计模式/image-20211205131022760.png)
> 实例代码
- LoadBalancer负载均衡器类充当**单例角色**
- Client**客户端测试类**
> 结果及分析
```java
//判断服务器负载均衡器是否相同
if (balancer1 == balancer2 && balancer2 == balancer3 && balancer3 == balancer4) {
System.out.println("服务器负载均衡器具有唯一性!");
}
```
## 饿汉式单例与懒汉式单例
> 饿汉式单例图
- 饿汉式单例类图(Eager Singleton)
![image-20211205131251186](设计模式/image-20211205131251186.png)
- 饿汉式单例代码
```java
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
 
public static EagerSingleton getInstance() {
return instance;
}
}
```
> 懒汉式单例类与双重检查锁定
- 延迟加载
```java
public class LazySingleton {
private static LazySingleton instance = null;
 
private LazySingleton() { }
 
// 多个线程同时访问将导致创建多个单例对象!怎么办?
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton(); // 需要较长时间
}
return instance;
}
}
```
- 锁方法
```java
public class LazySingleton {
private static LazySingleton instance = null;
 
private LazySingleton() { }
 
// 锁方法
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
```
- 锁代码段
```java
// ……
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) { // 锁代码段
instance = new LazySingleton();
}
}
return instance;
}
// ……
```
- Double-Check Locking双重检查锁定
```java
public class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判断
if (instance == null) {
//锁定代码块
synchronized (LazySingleton.class) {
//第二重判断
if (instance == null) {
instance = new LazySingleton(); //创建单例实例
}
}
}
return instance;
}
}
```
> 饿汉式单例类与懒汉式单例类的比较
- **饿汉式单例类**:无须考虑多个线程同时访问的问题;调用速度和反应时间优于懒汉式单例;资源利用效率不及懒汉式单例;系统加载时间可能会比较长
- **懒汉式单例类**:实现了延迟加载;必须处理好多个线程同时访问的问题;需通过双重检查锁定等机制进行控制,将导致系统性能受到一定影响
> 使用静态内部类实现单例模式
- **Java语言中最好的实现方式**
- Initialization on Demand Holder (IoDH): 使用**静态内部类**(static inner class)
```java
//Initialization on Demand Holder
public class Singleton {
private Singleton() {
}
//静态内部类
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
public static void main(String args[]) {
Singleton s1, s2;
s1 = Singleton.getInstance();
s2 = Singleton.getInstance();
System.out.println(s1==s2);
}
}
```
## 单例模式的优缺点与适用环境
> 模式优点
- 提供了**对唯一实例的受控访问**
- 可以**节约系统资源,提高系统的性能**
- 允许可变数目的实例(**多例类**
> 模式缺点
- **扩展困难**(缺少抽象层)
- 单例类的**职责过重**
- 由于自动垃圾回收机制,可能会导致共享的单例对象的**状态丢失**
> 模式适用环境
- 系统**只需要一个实例对象**,或者因为资源消耗太大而**只允许创建一个对象**
- 客户调用类的单个实例**只允许使用一个公共访问点**,除了该公共访问点,不能通过其他途径访问该实例
## 思考题
> 设计一个多例类(Multition),用户可以自行指定实例个数。
>
> ![image-20211205132724215](设计模式/image-20211205132724215.png)
代码实现:
```java
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
public class Multition {
private Multition(){
}
// 存储多个实例的对象
private static List<Multition> array = new ArrayList<>();
// 返回一个单例
public static Multition getInstance() {
if (array.size() == 0) {
System.out.println("请先调用random方法初始化多个实例个数");
}
Random random = new Random();
return array.get(random.nextInt(array.size()));
}
// 将多个实例初始化
public static void random(int count) {
for (int i = 0; i < count; i++) {
array.add(new Multition());
}
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入的要创建的实例个数:");
int count = input.nextInt();
Multition.random(count);
System.out.println("===成功创建【"+count+"】个实例!===");
System.out.println("【1】调用一个实例");
System.out.println("【2】退出系统");
while (true) {
switch (input.nextInt()) {
case 1 :
System.out.println("返回一个实例【"+Multition.getInstance()+"】");
break;
case 2 :
System.out.println("退出系统");
return;
default:
System.out.println("输入错误!请重新输入:");
break;
}
}
}
}
```
测试结果:
```
请输入的要创建的实例个数:
5
===成功创建【5】个实例===
【1】调用一个实例
【2】退出系统
1
返回一个实例【Multition@12a3a380】
1
返回一个实例【Multition@29453f44】
1
返回一个实例【Multition@5cad8086】
1
返回一个实例【Multition@6e0be858】
1
返回一个实例【Multition@12a3a380】
1
返回一个实例【Multition@29453f44】
1
返回一个实例【Multition@29453f44】
1
返回一个实例【Multition@6e0be858】
1
返回一个实例【Multition@12a3a380】
1
返回一个实例【Multition@12a3a380】
1
返回一个实例【Multition@12a3a380】
1
返回一个实例【Multition@61bbe9ba】
1
返回一个实例【Multition@29453f44】
1
返回一个实例【Multition@61bbe9ba】
2
退出系统
Process finished with exit code 0
```

@ -0,0 +1,219 @@
## 结构性模式概述
- 结构型模式(Structural Pattern)**关注如何将现有类或对象组织在一起形成更加强大的结构**
- 不同的结构型模式**从不同的角度组合类或对象**,它们在尽可能满足各种面向对象设计原则的同时为类或对象的组合提供一系列巧妙的解决方案
![image-20211130144846458](设计模式/image-20211130144846458.png)
> 类结构型和对象结构型
**类结构型模式**
- **关心类的组合**,由多个类组合成一个更大的系统,在类结构型模式中一般只存在**继承关系和实现关系**
**对象结构型模式**
- **关心类与对象的组合**,通过**关联关系**,在一个类中定义另一个类的实例对象,然后通过该对象调用相应的方法
> 结构型模式一览表
| **模式名称** | **定** **义** | **学习难度** | **使用频率** |
| -------------------------------- | ------------------------------------------------------------ | ------------ | ------------ |
| **适配器模式(Adapter Pattern)** | **将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。** | ★★☆☆☆ | ★★★★☆ |
| **桥接模式(Bridge Pattern)** | **将抽象部分与它的实现部分解耦,使得两者都能够独立变化。** | ★★★☆☆ | ★★★☆☆ |
| **组合模式(Composite Pattern)** | **组合多个对象形成树形结构,以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。** | ★★★☆☆ | ★★★★☆ |
| **装饰模式(Decorator Pattern)** | **动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。** | ★★★☆☆ | ★★★☆☆ |
| **外观模式****(Facade Pattern)** | **为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。** | ★☆☆☆☆ | ★★★★★ |
| **享元模式(Flyweight Pattern)** | **运用共享技术有效地支持大量细粒度对象的复用。** | ★★★★☆ | ★☆☆☆☆ |
| **代理模式(Proxy Pattern)** | **给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。** | ★★★☆☆ | ★★★★☆ |
## 适配器模式概述
![image-20211130145505176](设计模式/image-20211130145505176-16382553072691.png)
> 分析
现实生活:
- 不兼容生活用电220V  笔记电脑20V
- 引入 AC Adapter交流电适配器
软件开发:
- 存在不兼容的结构,例如方法名不一致
- 引入适配器模式
> 适配器模式的定义
适配器模式:将一个类的接口转换成客户希望的另一个接口。适配器模式**让那些接口不兼容的类可以一起工作。**
Adapter Pattern: Convert the interface of a class into another interface clients expect. **Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.**
- **对象结构型**模式 / **类结构型**模式
- 别名为**包装器(Wrapper)模式**
- 定义中所提及的接口是指广义的接口,它**可以表示一个方法或者方法的集合**
## 结构性模式的结构和实现
> 适配器模式的结构(类适配器)
![image-20211130145956876](设计模式/image-20211130145956876.png)
> 适配器模式的结构(对象适配器)
![image-20211130150041562](设计模式/image-20211130150041562.png)
> 适配器模式的结构
适配器模式包含以下3个角色
- Target目标抽象类
- Adapter适配器类
- Adaptee适配者类
> 适配器模式的实现
- 典型的**类适配器**代码:
```java
public class Adapter extends Adaptee implements Target {
public void request() {
super.specificRequest();
}
}
```
- 典型的**对象适配器**代码:
```java
public class Adapter extends Target {
private Adaptee adaptee; //维持一个对适配者对象的引用
public Adapter(Adaptee adaptee) {
this.adaptee=adaptee;
}
public void request() {
adaptee.specificRequest(); //转发调用
}
}
```
## 适配器模式的应用实例
> 实例说明
![image-20211130150412164](设计模式/image-20211130150412164.png)
某公司欲开发一款儿童玩具汽车,为了更好地吸引小朋友的注意力,该玩具汽车在移动过程中伴随着灯光闪烁和声音提示。在该公司以往的产品中已经实现了控制灯光闪烁(例如警灯闪烁)和声音提示(例如警笛音效)的程序,为了重用先前的代码并且使得汽车控制软件具有更好的灵活性和扩展性,现使用适配器模式设计该玩具汽车控制软件。
> 实例类图
![image-20211130150431841](设计模式/image-20211130150431841.png)
> 实例代码
- CarController汽车控制类充当目标抽象类
- PoliceSound警笛类充当适配者
- PoliceLamp警灯类充当适配者
- PoliceCarAdapter警车适配器充当适配器
- Client客户端测试类
- XMLUtil工具类
> 结果及分析
- 将具体适配器类的类名存储在配置文件中
- 扩展方便
```xml
<?xml version="1.0"?>
<config>
<className>designpatterns.adapter.PoliceCarAdapter</className>
</config>
```
## 缺省适配器模式
> 定义
**缺省适配器模式(Default Adapter Pattern)**当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为**单接口适配器模式。**
> 结构
![image-20211130151023771](设计模式/image-20211130151023771.png)
> 实现
缺省适配器类的典型代码片段:
```java
public abstract class AbstractServiceClass implements ServiceInterface {
public void serviceMethod1() { } //空方法
public void serviceMethod2() { } //空方法
public void serviceMethod3() { } //空方法
}
```
## 双向适配器
> 结构
![image-20211130151215298](设计模式/image-20211130151215298.png)
> 实现
双向适配器的典型代码片段
```java
public class Adapter implements Target,Adaptee {
private Target target;
private Adaptee adaptee;
public Adapter(Target target) {
this.target = target;
}
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
public void specificRequest() {
target.request();
}
}
```
## 适配器模式的优缺点与适用环境
> 模式优点
- 将**目标类和适配者类解耦**,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构
- **增加了类的透明性和复用性**,提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用
- **灵活性和扩展性非常好**
- **类适配器模式:**置换一些适配者的方法很方便
- **对象适配器模式:**可以把多个不同的适配者适配到同一个目标,还可以适配一个适配者的子类
> 模式缺点
类适配器模式:
- 一次最多只能适配一个适配者类,不能同时适配多个适配者;
- 适配者类不能为最终类;
- 目标抽象类只能为接口,不能为类
对象适配器模式:
- 在适配器中置换适配者类的某些方法比较麻烦
> 模式适用环境
- 系统**需要使用一些现有的类**,而这些类的接口不符合系统的需要,甚至没有这些类的源代码
- **创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类**,包括一些可能在将来引进的类**一起工作**
## 思考题
> 在对象适配器中,一个适配器能否适配多个适配者?如果能,应该如何实现?如果不能,请说明原因?如果是类适配器呢?

@ -0,0 +1,162 @@
## 桥接模式概述
> 毛笔与蜡笔的故事
![image-20211130161348960](设计模式/image-20211130161348960.png)
![image-20211130161400796](设计模式/image-20211130161400796.png)
> 分析
- **蜡笔**:颜色和型号**两个不同的变化维度**(即两个不同的变化原因)耦合在一起,无论是对颜色进行扩展还是对型号进行扩展都势必会影响另一个维度
- **毛笔**:颜色和型号实现了分离,增加新的颜色或者型号对另一方没有任何影响
![image-20211130161506107](设计模式/image-20211130161506107.png)
> 在软件开发中使用桥接模式将多个变化维度分离
![image-20211130161623044](设计模式/image-20211130161623044.png)
> 桥接模式的定义
桥接模式:将**抽象部分**与它的**实现部分**解耦,使得两者都能够独立变化。
Bridge Pattern: **Decouple** an **abstraction** from its **implementation** so that the two can vary independently.
- **对象结构型**模式
- 又被称为**柄体(Handle and Body)**模式或**接口(Interface)**模式
- 用**抽象关联**取代了传统的多层继承
- 将类之间的静态继承关系转换为**动态的对象组合关系**
## 桥接模式的结构与实现
> 桥接模式的结构
![image-20211130161845193](设计模式/image-20211130161845193.png)
> 桥接模式包含以下4个对象
- Abstraction抽象类
- RefinedAbstraction扩充抽象类
- Implementor实现类接口
- ConcreteImplementor具体实现类
> 桥接模式的实现
![image-20211130162027841](设计模式/image-20211130162027841.png)
- 典型的**实现类接口**代码:
```java
public interface Implementor {
public void operationImpl();
}
```
- 典型的**具体实现类**代码:
```java
public class ConcreteImplementor implements Implementor {
public void operationImpl() {
//具体业务方法的实现
}
}
```
- 典型的**抽象类**代码
```java
public abstract class Abstraction {
protected Implementor impl; //定义实现类接口对象
public void setImpl(Implementor impl) {
this.impl=impl;
}
public abstract void operation(); //声明抽象业务方法
}
```
- 典型的扩充抽象类(细化抽象类)代码:
```java
public class RefinedAbstraction extends Abstraction {
public void operation() {
//业务代码
impl.operationImpl(); //调用实现类的方法
//业务代码
}
}
```
## 桥接模式的应用实例
> 实例说明
某软件公司要开发一个跨平台图像浏览系统要求该系统能够显示BMP、JPG、GIF、PNG等多种格式的文件并且能够在Windows、Linux、UNIX等多个操作系统上运行。系统首先将各种格式的文件解析为像素矩阵(Matrix),然后将像素矩阵显示在屏幕上,在不同的操作系统中可以调用不同的绘制函数来绘制像素矩阵。另外,系统需具有较好的扩展性,以便在将来支持新的文件格式和操作系统。试使用桥接模式设计该跨平台图像浏览系统。
> 实例类图
![image-20211130162420814](设计模式/image-20211130162420814.png)
> 实例代码
- Matrix像素矩阵类辅助类
- ImageImp抽象操作系统实现类充当实现类接口
- WindowsImpWindows操作系统实现类充当具体实现类
- LinuxImpLinux操作系统实现类充当具体实现类
- UnixImpUNIX操作系统实现类充当具体实现类
- Image抽象图像类充当抽象类
- JPGImageJPG格式图像类充当扩充抽象类
- PNGImagePNG格式图像类充当扩充抽象类
- BMPImageBMP格式图像类充当扩充抽象类
- GIFImageGIF格式图像类充当扩充抽象类
- Client客户端测试类
> 结果及分析
- 如果需要更换图像文件格式或者更换操作系统,只需修改**配置文件**即可
```xml
<?xml version="1.0"?>
<config>
<!--RefinedAbstraction-->
<className>designpatterns.bridge.JPGImage</className>
<!--ConcreteImplementor-->
<className>designpatterns.bridge.WindowsImp</className>
</config>
```
## 桥接模式与适配器模式的联用
- **桥接模式**:用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色,使它们可以分别进行变化
- **适配器模式**:当发现系统与已有类无法协同工作时
![image-20211130162738170](设计模式/image-20211130162738170.png)
## 桥接模式的优缺点与适用环境
> 模式优点
- **分离抽象接口及其实现部分**
- 可以取代多层继承方案,**极大地减少了子类的个数**
- **提高了系统的可扩展性**,在两个变化维度中任意扩展一个维度,不需要修改原有系统,符合开闭原则
> 模式缺点
- 会增加**系统的理解与设计难度**,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程
- **正确识别出系统中两个独立变化的维度并不是一件容易的事情**
> 模式适用环境
- 需要在抽象化和具体化之间增加更多的灵活性,**避免在两个层次之间建立静态的继承关系**
- 抽象部分和实现部分可以以继承的方式**独立扩展而互不影响**
- 一个类**存在两个(或多个)独立变化的维度**,且这两个(或多个)维度都需要独立地进行扩展
- **不希望使用继承**或**因为多层继承导致系统类的个数急剧增加**的系统
## 思考题
> 如果系统中存在两个以上的变化维度,是否可以使用桥接模式进行处理?如果可以,系统该如何设计?

@ -0,0 +1,203 @@
## 组合模式概述
- Windows操作系统目录结构
![image-20211205142104008](设计模式/image-20211205142104008.png)
> 分析
- 在树形目录结构中,包含**文件和文件夹**两类不同的元素
- **在文件夹中可以包含文件,还可以继续包含子文件夹**
- **在文件中不能再包含子文件或者子文件夹**
- 文件夹 <— > **容器(Container)**
- 文件 <— > **叶子(Leaf)**
- 当容器对象的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象并调用执行,牵一而动百,其中**使用了递归调用的机制**来对整个结构进行处理
- 由于容器对象和叶子对象在功能上的区别,**在使用这些对象的代码中必须有区别地对待容器对象和叶子对象**,而实际上大多数情况下客户端**希望一致地处理它们**,因为对于这些对象的区别对待将会使程序非常复杂
```java
if (is 容器对象) {
//处理容器对象
}
else if (is 叶子对象) {
//处理叶子对象
}
```
> 如何**一致地对待容器对象和叶子对象?**
组合模式通过一种巧妙的设计方案使得用户可以一致性地**处理整个树形结构或者树形结构的一部分**,它描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象。
> 组合模式的定义
**组合模式:**组合多个对象形成**树形结构**以表示**具有部分-整体关系的层次结构**。组合模式让客户端可以**统一**对待单个对象和组合对象。
**Composite Pattern:** Compose objects into **tree structures** to represent **part-whole hierarchies**. Composite lets clients treat individual objects and compositions of objects **uniformly**.
- **对象结构型**模式
- 又称为**“部分-整体”(Part-Whole)**模式
- 将对象组织到**树形结构**中,可以用来描述整体与部分的关系
## 组合模式的结构与实现
> 组合模式的结构
![image-20211205142938106](设计模式/image-20211205142938106.png)
> 组合模式包含以下3个角色
- Component抽象构件它可以是接口或抽象类为叶子构件和容器构件对象声明接口在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法如增加子构件、删除子构件、获取子构件等。
- Leaf叶子构件它在组合结构中表示叶子节点对象叶子节点没有子节点它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法可以通过异常等方式进行处理。
- Composite容器构件它在组合结构中表示容器节点对象容器节点包含子节点其子节点可以是叶子节点也可以是容器节点它提供一个集合用于存储子节点实现了在抽象构件中定义的行为包括那些访问及管理子构件的方法在其业务方法中可以递归调用其子节点的业务方法。
> 组合模式的实现
- **抽象构件角色**典型代码:
```java
public abstract class Component {
public abstract void add(Component c); //增加成员
public abstract void remove(Component c); //删除成员
public abstract Component getChild(int i); //获取成员
public abstract void operation(); //业务方法
}
```
- **叶子构件角色**典型代码:
```java
public class Leaf extends Component {
public void add(Component c) {
//异常处理或错误提示
}
public void remove(Component c) {
//异常处理或错误提示
}
public Component getChild(int i) {
//异常处理或错误提示
return null;
}
public void operation() {
//叶子构件具体业务方法的实现
}
}
```
- **容器构件**角色典型代码:
```java
public class Composite extends Component {
private ArrayList<Component> list = new ArrayList<Component>();
public void add(Component c) {
list.add(c);
}
public void remove(Component c) {
list.remove(c);
}
public Component getChild(int i) {
return (Component)list.get(i);
}
public void operation() {
//容器构件具体业务方法的实现,将递归调用成员构件的业务方法
for(Object obj:list) {
((Component)obj).operation();
}
}
}
```
## 组合模式的应用实例
> 实例说明
某软件公司欲开发一个杀毒(Antivirus)软件,该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒。该杀毒软件还可以根据各类文件的特点,为不同类型的文件提供不同的杀毒方式,例如图像文件(ImageFile)和文本文件(TextFile)的杀毒方式就有所差异。现使用组合模式来设计该杀毒软件的整体框架。
> 实例类图
![image-20211205143357441](设计模式/image-20211205143357441.png)
> 实例代码
- AbstractFile抽象文件类充当抽象构件类
- ImageFile图像文件类充当叶子构件类
- TextFile文本文件类充当叶子构件类
- VideoFile视频文件类充当叶子构件类
- Folder文件夹类充当容器构件类
- Client客户端测试类
> 结果及分析
- 如果需要更换操作节点,例如只对文件夹“文本文件”进行杀毒,客户端代码只需修**改一行即可**,例如将代码:
`floder.killVirus();`
改为:
`floder3.killVirus()`
- 在具体实现时,可以**创建图形化界面让用户来选择所需操作的根节点**,无须修改源代码,符合开闭原则
## 透明组合模式与安全组合模式
> 透明组合模式
- 抽象构件Component中**声明了所有用于管理成员对象的方法包括add()、remove()以及getChild()等方法**
- 在客户端看来,叶子对象与容器对象所提供的方法是一致的,**客户端可以一致地对待所有的对象**
- 缺点是**不够安全**,因为叶子对象和容器对象在本质上是有区别的
![image-20211205143904578](设计模式/image-20211205143904578.png)
> 安全组合模式
- 抽象构件Component中**没有声明任何用于管理成员对象的方法而是在Composite类中声明并实现这些方法**
- **对于叶子对象,客户端不可能调用到这些方法**
- 缺点是**不够透明,客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件**
![image-20211205144027313](设计模式/image-20211205144027313.png)
> Java AWT中的组件树
![image-20211205144114426](设计模式/image-20211205144114426.png)
## 组合模式的优缺点与适用环境
> 模式优点
- 可以清楚地**定义分层次的复杂对象**,表示对象的全部或部分层次,**让客户端忽略了层次的差异**,方便对整个层次结构进行控制
- 客户端可以**一致地使用一个组合结构或其中单个对象**,不必关心处理的是单个对象还是整个组合结构,**简化了客户端代码**
- **增加新的容器构件和叶子构件都很方便**,符合开闭原则
- 为**树形结构的面向对象实现**提供了一种灵活的解决方案
> 模式缺点
- 在增加新构件时**很难对容器中的构件类型进行限制**
> 模式适用环境
- 在**具有整体和部分的层次结构**中,希望通过一种方式忽略整体与部分的差异,**客户端可以一致地对待它们**
- 在一个使用**面向对象语言开发的系统**中需要处理一个**树形结构**
- 在一个系统中**能够分离出叶子对象和容器对象**,而且它们的类型不固定,**需要增加一些新的类型**
## 思考题
> 在组合模式的结构图中如果聚合关联关系不是从Composite到Component的而是从Composite到Leaf如下图所示会产生怎样的结果
>
> ![image-20211205144408142](设计模式/image-20211205144408142.png)
聚合关联关系改为从构件(Composite)到叶子节点(Leaf),说明构件只能包含叶子节点不能够包含其他构件了。代码的实现上如果简单处理的话,在在容器类中添加一个判断是否属于自己合格子节点的方法交由构件自己实现,比如在这里
```java
if(isinstanceof(Leaf)) {
return true;
} else {
return false;
}
```
构件具体实现在添加子节点的时候调用这个合格判断的方法,不合格不添加进构件。

@ -0,0 +1,225 @@
## 装饰模式概述
> 举例子
![image-20211129081613170](设计模式/image-20211129081613170.png)
> 装饰模式分析
- 可以**在不改变一个对象本身功能的基础上给对象增加额外的新行为**
- 是一种**用于替代继承的技术**,它通过一种无须定义子类的方式**给对象动态增加职责**,使用对象之间的**关联关系**取代类之间的**继承关系**
- 引入了**装饰类**,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩展原有类的功能
> 装饰结构性模式的定义
**装饰模式****动态地**给一个对象**增加一些额外的职责**。就扩展功能而言,装饰模式提供了一种**比使用子类更加灵活的替代方案**。
**Decorator Pattern**: **Attach additional responsibilities** to an object **dynamically**. Decorators provide **a flexible alternative to subclassing** for extending functionality.
- **对象结构型**模式
- 以对客户透明的方式**动态地给一个对象附加上更多的责任**
- 可以在**不需要创建更多子类**的情况下,让对象的功能得以扩展
## 装饰模式的结构与实现
> 图例
![image-20211129082300557](设计模式/image-20211129082300557.png)
> 包含以下四个角色
- Component抽象构件
- ConcreteComponent具体构件
- Decorator抽象装饰类
- ConcreteDecorator具体装饰类
> 装饰模式的实现
- **抽象构件类**典型代码
```java
public abstract class Component {
public abstract void operation();
}
```
- **具体构件类**典型代码
```java
public class ConcreteComponent extends Component {
public void operation() {
// 此处实现基本功能
}
}
```
- **抽象装饰类**典型代码
```java
public class Decorator extends Component {
private Component component; //维持一个对抽象构件对象的引用
//注入一个抽象构件类型的对象
public Decorator(Component component) {
this.component=component;
}
public void operation() {
component.operation(); //调用原有业务方法
}
}
```
- **具体装饰类**典型代码
```java
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation(); //调用原有业务方法
addedBehavior(); //调用新增业务方法
}
// 新增业务方法
public void addedBehavior() {
// ……
}
}
```
## 装饰模式的应用实例
> 实例说明
某软件公司基于面向对象技术开发了一套图形界面构件库——VisualComponent该构件库提供了大量基本构件如窗体、文本框、列表框等由于在使用该构件库时用户经常要求定制一些特殊的显示效果如带滚动条的窗体、带黑色边框的文本框、既带滚动条又带黑色边框的列表框等等因此经常需要对该构件库进行扩展以增强其功能。
> 实例类图
![image-20211129083300118](设计模式/image-20211129083300118.png)
- Component抽象界面构件类充当**抽象构件类**
- Window窗体充当类充当**具体构件类**
- TextBox文本框类充当**具体构件类**
- ListBox列表框类充当**具体构件类**
- ComponentDecorator构件装饰类充当**抽象装饰类**
- ScrollBarDecorator滚动条装饰类充当**具体装饰类**
- BlackBorderDecorator黑色边框装饰类充当**具体装饰类**
- Client客户端测试类
> 结果及分析
```java
public class Client {
public static void main(String args[]) {
Component component, componentSB, componentBB;
// 显示窗体
component = new Window();
// 继承原本的component窗体增加滚动条
componentSB = new ScrollBarDecorator(component);
// 继承原来的componentSB窗体、滚动条增加黑色边框
componentBB = new BlackBorderDecorator(componentSB);
componentBB.display();
}
}
```
## 透明/半透明装饰模式
### 透明装饰模式
- 透明(Transparent)装饰模式:要求**客户端完全针对抽象编程**,装饰模式的透明性要求**客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件类型**
- 对于客户端而言,**具体构件对象和具体装饰对象没有任何区别**
- 可以**让客户端透明地使用装饰之前的对象和装饰之后的对象**,无须关心它们的区别
- **可以对一个已装饰过的对象进行多次装饰**,得到更为复杂、功能更为强大的对象
- **无法在客户端单独调用新增方法addedBehavior()**因为对象声明时为上转化对象不包含新增方法addedBehavior()
```java
// ……
Component component_o,component_d1,component_d2; //全部使用抽象构件定义
component_o = new ConcreteComponent();
component_d1 = new ConcreteDecorator1(component_o);
component_d2 = new ConcreteDecorator2(component_d1);
component_d2.operation();
// 无法单独调用component_d2的addedBehavior()方法
// ……
```
### 半透明装饰模式
- 半透明(Semi-transparent)装饰模式:**用具体装饰类型来定义装饰之后的对象,而具体构件使用抽象构件类型来定义**
- 对于客户端而言,**具体构件类型无须关心,是透明的**;但是**具体装饰类型必须指定,这是不透明的**
- 可以给系统带来更多的灵活性,设计相对简单,使用起来也非常方便
- 客户端使用具体装饰类型来定义装饰后的对象,因此**可以单独调用addedBehavior()方法**
- 最大的缺点在于**不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象**
```java
// ……
Component component_o; //使用抽象构件类型定义
component_o = new ConcreteComponent();
component_o.operation();
ConcreteDecorator component_d; //使用具体装饰类型定义
component_d = new ConcreteDecorator(component_o);
component_d.operation();
component_d.addedBehavior(); //单独调用新增业务方法
// ……
```
## 装饰模式的优缺点与适用环境
> 优点
- 对于扩展一个对象的功能,**装饰模式比继承更加灵活,不会导致类的个数急剧增加**
- 可以**通过一种动态的方式来扩展一个对象的功能**,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为
- 可以对一个对象进行**多次装饰**
- 具体构件类与具体装饰类可以独立变化,用户**可以根据需要增加新的具体构件类和具体装饰类**,且原有类库代码无须改变,**符合开闭原则**
> 缺点
- 使用装饰模式进行**系统设计时将产生很多小对象**,大量小对象的产生势必会占用更多的系统资源,**在一定程度上影响程序的性能**
- **比继承更加易于出错,排错也更困难**,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐
> 模式适用环境
- 在不影响其他对象的情况下,**以动态、透明的方式给单个对象添加职责**
- 当**不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时**可以使用装饰模式
## 思考题
> 半透明装饰模式能否实现对同一个对象的多次装饰?为什么?
不能!
透明模式
```java
// ……
Component component_o,component_d1,component_d2; //全部使用抽象构件定义
component_o = new ConcreteComponent();
component_d1 = new ConcreteDecorator1(component_o);
component_d2 = new ConcreteDecorator2(component_d1);
component_d2.operation();
// 无法单独调用component_d2的addedBehavior()方法
// ……
```
半透明模式
```java
// ……
Component component_o; //使用抽象构件类型定义
component_o = new ConcreteComponent();
component_o.operation();
ConcreteDecorator component_d; //使用具体装饰类型定义
component_d = new ConcreteDecorator(component_o);
component_d.operation();
component_d.addedBehavior(); //单独调用新增业务方法
// ……
```
透明模式:`component_o,component_d1,component_d2`都是用的抽象构件定义当new一个具体的装饰类的时候会先调用`super(component)`,从而当调用`component_d2.operation()`时候会执行`super.operation()`,所以执行顺序应该是`component_o.operation()`、`component_d1.operation()`、`component_d2.operation()`(按照方法出栈的顺序),从而可以实现多次装饰。
半透明方式:调用的方法已经不是最开始抽象构件里就有的方法了,是它自己后面新增的方法了,已经不满足这个大前提了,所以自然不能实现对同一对象的多次装饰。半透明模式强调的是调用新增的方法,用具体的装饰类型来定义装饰之后的对象。

@ -0,0 +1,195 @@
## 外观模式概述
> 两种喝茶方式示意图
![image-20211130172746776](设计模式/image-20211130172746776.png)
> 分析
- 一个客户类需要和**多个业务类**交互,而这些需要交互的业务类经常会作为一个整体出现
- 引入一个新的**外观类(Facade)**来负责和多个业务类**【子系统(Subsystem)】**进行交互,而客户类只需与外观类交互
- 为多个业务类的调用**提供了一个统一的入口,简化了类与类之间的交互**
- **没有外观类**:每个客户类需要和多个子系统之间进行复杂的交互,**系统的耦合度将很大**
- **引入外观类**:客户类只需要直接与外观类交互,客户类与子系统之间原有的复杂引用关系由外观类来实现,从而**降低了系统的耦合度**
![image-20211130173138631](设计模式/image-20211130173138631.png)
- 一个子系统的外部与其内部的通信通过一个统一的外观类进行,**外观类将客户类与子系统的内部复杂性分隔开**,使得客户类只需要与外观角色打交道,而不需要与子系统内部的很多对象打交道
- 为复杂子系统提供一个简单的访问入口
![image-20211130173242758](设计模式/image-20211130173242758.png)
> 外观模式的定义
外观模式:为子系统中的一组接口提供一个**统一的入口**。外观模式定义了**一个高层接口**,这个接口使得这一子系统更加容易使用。
Facade Pattern: Provide **a unified interface** to a set of interfaces in a subsystem. Facade defines **a higher-level interface** that makes the subsystem easier to use.
- **对象结构型**模式
- 又称为**门面模式**
- 是**迪米特法则**的一种具体实现
- 通过**引入一个新的外观角色**来**降低原有系统的复杂度**,同时降低客户类与子系统的耦合度
- 所指的**子系统**是一个广义的概念,它可以是一个类、一个功能模块、系统的一个组成部分或者一个完整的系统
## 外观模式的结构与实现
>外观模式的结构
![image-20211130174417027](设计模式/image-20211130174417027.png)
> 包含以下2个角色
- Facade外观角色
- SubSystem子系统角色
> 外观模式的实现
- **子系统类**典型代码
```java
public class SubSystemA {
public void methodA() {
//业务实现代码
}
}
public class SubSystemB {
public void methodB() {
//业务实现代码
}
}
public class SubSystemC {
public void methodC() {
//业务实现代码
}
}
```
- **外观类**典型代码
```java
public class Facade {
private SubSystemA obj1 = new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
private SubSystemC obj3 = new SubSystemC();
public void method() {
obj1.method();
obj2.method();
obj3.method();
}
}
```
- **客户类**典型代码
```java
public class Client {
public static void main(String args[]) {
Facade facade = new Facade();
facade.method();
}
}
```
## 外观模式的应用实例
> 实例说明
某软件公司要开发一个可应用于多个软件的文件加密模块该模块可以对文件中的数据进行加密并将加密之后的数据存储在一个新文件中具体的流程包括3个部分分别是读取源文件、加密、保存加密之后的文件其中读取文件和保存文件使用流来实现加密操作通过求模运算实现。这3个操作相对独立为了实现代码的独立重用让设计更符合单一职责原则这3个操作的业务代码封装在3个不同的类中。
现使用外观模式设计该文件加密模块。
![image-20211130185748886](设计模式/image-20211130185748886.png)
> 实例代码
- FileReader文件读取类充当子系统类
- CipherMachine数据加密类充当子系统类
- FileWriter文件保存类充当子系统类
- EncryptFacade加密外观类充当外观类
- Client客户端测试类
## 抽象外观类
> 动机
在标准的外观模式的结构图中,如果需要增加、删除或更换与外观类交互的子系统类,**必须修改外观类或客户端的源代码**,这将**违背开闭原则**,因此可以通过引入**抽象外观类**对系统进行改进,在一定程度上解决该问题
> 结构
![image-20211130190000139](设计模式/image-20211130190000139.png)
> 代码
抽象外观类AbstractEncryptFacade.java
```
public abstract class AbstractEncryptFacade {
public abstract void fileEncrypt(String fileNameSrc, String fileNameDes);
}
```
具体外观类NewEncryptFacade.java
```java
public class NewEncryptFacade extends AbstractEncryptFacade {
private FileReader reader;
private NewCipherMachine cipher;
private FileWriter writer;
public NewEncryptFacade() {
reader = new FileReader();
cipher = new NewCipherMachine();
writer = new FileWriter();
}
public void fileEncrypt(String fileNameSrc, String fileNameDes) {
String plainStr = reader.read(fileNameSrc);
String encryptStr = cipher.encrypt(plainStr);
writer.write(encryptStr,fileNameDes);
}
}
```
配置文件config.xml
```xml
<?xml version="1.0"?>
<config>
<className>designpatterns.facade.NewEncryptFacade</className>
</config>
```
客户端测试类client.java
```java
public class Client {
public static void main(String args[]) {
AbstractEncryptFacade ef;
ef = (AbstractEncryptFacade)XMLUtil.getBean();
ef.fileEncrypt("src//designpatterns//facade//src.txt","src//designpatterns //facade//des.txt"); }
}
```
## 外观模式与单例模式联用
> 将外观模式改造为单例类
![image-20211130190503218](设计模式/image-20211130190503218.png)
## 外观模式的优缺点与适用环境
> 模式优点
- 它**对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易**
- 它**实现了子系统与客户端之间的松耦合关系**,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可
- **一个子系统的修改对其他子系统没有任何影响**,而且**子系统的内部变化也不会影响到外观对象**
> 模式缺点
- **不能很好地限制客户端直接使用子系统类**,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性
- 如果**设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则**
> 模式适用环境
- 要为访问**一系列复杂的子系统提供一个简单入口**
- **客户端程序与多个子系统之间存在很大的依赖性**
- 在层次化结构中,可以**使用外观模式的定义系统中每一层的入口**,层与层之间不直接产生联系,而是通过外观类建立联系,**降低层之间的耦合度**

@ -0,0 +1,251 @@
## 享元模式概述
> 动机
- 如果一个软件系统**在运行时所创建的相同或相似对象数量太多,将导致运行代价过高,带来系统资源浪费、性能下降等问题**
- 如何**避免系统中出现大量相同或相似的对象**,同时又不影响客户端程序通过面向对象的方式对这些对象进行操作呢?
> 字符享元对象示意图
![image-20211205145008794](设计模式/image-20211205145008794.png)
> 分析
- **享元模式**:通过**共享技术**实现相同或相似对象的**重用**
- **享元池(Flyweight Pool)**:存储共享实例对象的地方
![image-20211205145103362](设计模式/image-20211205145103362.png)
- 内部状态(Intrinsic State)**存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享**(例如:字符的内容)
- 外部状态(Extrinsic State)**随环境改变而改变的、不可以共享的状态。**享元对象的外部状态通常**由客户端保存**,并在享元对象被创建之后,需要使用的时候再**传入到享元对象内部**。一个外部状态与另一个外部状态之间是**相互独立**的(例如:字符的颜色和大小)
> 原理
1. 将**具有相同内部状态**的对象存储在**享元池**中,享元池中的对象是可以实现共享的
2. 需要的时候**将对象从享元池中取出**,即可实现对象的**复用**
3. 通过向取出的对象**注入不同的外部状态**,可以得到一系列**相似的对象**,而这些对象在内存中实际上只存储一份
> 享元模式的定义
**享元模式**:运用**共享技术**有效地支持大量细粒度对象的复用。
**Flyweight Pattern:** Use **sharing** to support large numbers of fine-grained objects efficiently.
- **对象行为型**模式
- 又称为**轻量级模式**
- 要求能够被共享的对象必须是**细粒度对象**
## 享元模式的结构与实现
> 享元模式的结构
![image-20211205145512381](设计模式/image-20211205145512381.png)
> 享元模式包含以下4个角色
- Flyweight抽象享元类
- ConcreteFlyweight具体享元类
- UnsharedConcreteFlyweight非共享具体享元类
- FlyweightFactory享元工厂类
> 享元模式的实现
- 典型的**抽象享元类**代码:
```java
public abstract class Flyweight {
public abstract void operation(String extrinsicState);
}
```
- 典型的**具体享元类**代码:
```java
public class ConcreteFlyweight extends Flyweight {
//内部状态intrinsicState作为成员变量同一个享元对象其内部状态是一致的
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
//外部状态extrinsicState在使用时由外部设置不保存在享元对象中即使是同一个对象在每一次调用时可以传入不同的外部状态
public void operation(String extrinsicState) {
//实现业务方法
}
}
```
- 典型的**非共享具体享元类**代码:
```java
public class UnsharedConcreteFlyweight extends Flyweight {
public void operation(String extrinsicState) {
//实现业务方法
}
}
```
- 典型的**享元工厂类**代码:
```java
public class FlyweightFactory {
//定义一个HashMap用于存储享元对象实现享元池
private HashMap flyweights = new HashMap();
public Flyweight getFlyweight(String key) {
//如果对象存在,则直接从享元池获取
if (flyweights.containsKey(key)) {
return (Flyweight)flyweights.get(key);
}
//如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
else {
Flyweight fw = new ConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
```
## 享元模式的应用实例
> 实例说明
某软件公司要开发一个围棋软件,其界面效果如下图所示:
![image-20211205145829705](设计模式/image-20211205145829705.png)
该软件公司开发人员通过对围棋软件进行分析发现,在图中,围棋棋盘中包含大量的黑子和白子,它们的形状、大小都一模一样,只是出现的位置不同而已。如果将每一个棋子都作为一个独立的对象存储在内存中,将导致该围棋软件在运行时所需内存空间较大,如何降低运行代价、提高系统性能是需要解决的一个问题。为了解决该问题,现使用享元模式来设计该围棋软件的棋子对象。
> 实例类图
![image-20211205145908114](设计模式/image-20211205145908114.png)
> 实例代码
- IgoChessman围棋棋子类充当抽象享元类
- BlackIgoChessman黑色棋子类充当具体享元类
- WhiteIgoChessman白色棋子类充当具体享元类
- IgoChessmanFactory围棋棋子工厂类充当享元工厂类
- Client客户端测试类
> 结果及分析
- 在实现享元工厂类时使用了**单例模式**和**简单工厂模式**,确保了享元工厂对象的唯一性,并提供了工厂方法向客户端返回享元对象
## 有外部状态的享元模式
> 动机
- 如何让相同的黑子或者白子**能够多次重复显示但位于一个棋盘的不同地方?**
- **解决方案**:将棋子的位置定义为棋子的一个**外部状态**,在需要时再进行设置
> 结构
![image-20211205150142585](设计模式/image-20211205150142585.png)
> 实现
```java
package designpatterns.flyweight.extend;
public class Coordinates {
private int x;
private int y;
public Coordinates(int x,int y) {
this.x = x;
this.y = y;
}
public int getX() {
return this.x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return this.y;
}
public void setY(int y) {
this.y = y;
}
}
```
```java
package designpatterns.flyweight.extend;
//围棋棋子类:抽象享元类
public abstract class IgoChessman {
public abstract String getColor();
public void display(Coordinates coord){
System.out.println("棋子颜色:" + this.getColor() + ",棋子位置:" + coord.getX() + "" + coord.getY() );
}
}
```
## 单纯享元模式和复合享元模式
> 单纯享元模式
- **所有的具体享元类都是可以共享的**,不存在非共享具体享元类
![image-20211205150308246](设计模式/image-20211205150308246.png)
> 复合享元模式
- 将一些单纯享元对象**使用组合模式加以组合**
- 如果希望**为多个内部状态不同的享元对象设置相同的外部状态**,可以考虑使用复合享元模式
![image-20211205150351241](设计模式/image-20211205150351241.png)
> 享元模式与String类
测试代码
```java
public class Demo {
public static void main(String args[]) {
String str1 = "abcd";
String str2 = "abcd";
String str3 = "ab" + "cd";
String str4 = "ab";
str4 += "cd";
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str1 == str4);
str2 += "e";
System.out.println(str1 == str2);
}
}
```
输出结果:
```
true
true
false
false
```
## 享元模式的优缺点与适用环境
> 模式优点
- 可以**减少内存中对象的数量****使得相同或者相似的对象在内存中只保存一份**,从而**可以节约系统资源,提高系统性能**
- 外部状态相对独立,而且不会影响其内部状态,从而使得**享元对象可以在不同的环境中被共享**
> 模式缺点
- **使得系统变得复杂**,需要分离出内部状态和外部状态,这**使得程序的逻辑复杂化**
- 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而**读取外部状态将使得运行时间变长**
> 模式适用环境
- **一个系统有大量相同或者相似的对象**,造成内存的大量耗费
- 对象的**大部分状态都可以外部化**,可以将这些外部状态传入对象中
- 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,**在需要多次重复使用享元对象时才值得使用享元模式**

@ -0,0 +1,198 @@
## 代理模式概述
> 商品代购示意图
![image-20211130195823044](设计模式/image-20211130195823044.png)
> 分析
- 代购商品:顾客 → 代购网站 → 商品
- 软件开发:客户端 → 代理对象 → 真实对象
![image-20211130200001003](设计模式/image-20211130200001003.png)
> 类型
![image-20211130200028067](设计模式/image-20211130200028067.png)
> 代理模式的定义
**代理模式**:给某一个对象提供**一个代理或占位符**,并由代理对象来控制对原对象的访问。
Proxy Pattern: Provide **a surrogate or placeholder** for another object to control access to it.
- **对象结构型**模式
- 引入一个新的**代理对象**
- 代理对象**在客户端对象和目标对象之间起到中介的作用**
- **去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务**
## 代理模式的结构与实现
> 代理模式的结构
![image-20211130200214129](设计模式/image-20211130200214129.png)
> 包含以下三个角色
| 角色 | 作用 |
| --------------------------- | :----------------------------------------------------------: |
| Subject抽象主题角色 | 声明真实对象和代理对象的共同接口。 |
| Proxy代理主题角色 | 代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能够代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。 |
| RealSubject真实主题角色 | 代理角色所代表的真实对象,是我们最终要引用的对象。 |
> 代理模式的实现
- **抽象主题类**典型代码
```java
public abstract class Subject {
public abstract void request();
}
```
- **真实主题类**典型代码
```java
public class RealSubject extends Subject{
public void request() {
// 业务方法具体实现代码
}
}
```
- **代理类**典型代码
```java
public class Proxy extends Subject {
private RealSubject realSubject = new RealSubject(); //维持一个对真实主题对象的引用 
public void preRequest() {
…...
}
 
public void request() {
preRequest();
realSubject.request(); //调用真实主题对象的方法
postRequest();
}
 
public void postRequest() {
……
}
}
```
> 常见的几种代理模式
- **远程代理(Remote Proxy)**:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以在另一台主机中,远程代理又称为大使(**Ambassador**)
- **虚拟代理(Virtual Proxy)**:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建
- **保护代理(Protect Proxy)**:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限
- **缓冲代理(Cache Proxy)**:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
- **智能引用代理(Smart Reference Proxy)**:当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等
## 代理模式的应用实例
> 实例说明
![image-20211130200630190](设计模式/image-20211130200630190.png)
某软件公司承接了某信息咨询公司的收费商务信息查询系统的开发任务,该系统的基本需求如下:
(1) 在进行商务信息查询之前用户需要通过身份验证,只有合法用户才能够使用该查询系统;
(2) 在进行商务信息查询时系统需要记录查询日志,以便根据查询次数收取查询费用。
该软件公司开发人员已完成了商务信息查询模块的开发任务,现希望能够以一种松耦合的方式向原有系统增加身份验证和日志记录功能,客户端代码可以无区别地对待原始的商务信息查询模块和增加新功能之后的商务信息查询模块,而且可能在将来还要在该信息查询模块中增加一些新的功能。
现使用代理模式设计并实现该收费商务信息查询系统。
> 实例分析及类图
![image-20211130200713055](设计模式/image-20211130200713055.png)
![image-20211130200733314](设计模式/image-20211130200733314.png)
> 实例代码
- AccessValidator身份验证类业务类
- Logger日志记录类业务类
- Searcher抽象查询类充当抽象主题角色
- RealSearcher具体查询类充当真实主题角色
- ProxySearcher代理查询类充当代理主题角色
- Client客户端测试类
> 结果及分析
- **保护代理**和**智能引用代理**
- 在代理类**ProxySearcher**中实现对真实主题类的**权限控制**和**引用计数**
## 远程代理
> 动机
- **客户端程序可以访问在远程主机上的对象**,远程主机可能具有更好的计算性能与处理速度,可以快速地响应并处理客户端的请求
- 可以**将网络的细节隐藏起来**,使得客户端不必考虑网络的存在
- 客户端完全可以认为被代理的远程业务对象是在本地而不是在远程,而**远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用**
> 结构
![image-20211130201013745](设计模式/image-20211130201013745.png)
## 虚拟代理
> 动机
![image-20211130201038616](设计模式/image-20211130201038616.png)
- 对于一些**占用系统资源较多或者加载时间较长的对象**,可以给这些对象提供一个虚拟代理
- 在真实对象创建成功之前**虚拟代理扮演真实对象的替身**,而当真实对象创建之后,**虚拟代理将用户的请求转发给真实对象**
- 使用一个“虚假”的代理对象来代表真实对象,通过代理对象来间接引用真实对象,可以**在一定程度上提高系统的性能**
> 应用
- 由于对象本身的复杂性或者网络等原因导致**一个对象需要较长的加载时间**,此时可以**用一个加载时间相对较短的代理对象来代表真实对象**(结合**多线程技术**
- **一个对象的加载十分耗费系统资源**,让那些占用大量内存或处理起来非常复杂的对象推迟到使用它们的时候才创建,而在此之前**用一个相对来说占用资源较少的代理对象来代表真实对象,再通过代理对象来引用真实对象****用时间换取空间**
## Java动态代理
- **动态代理(Dynamic Proxy)**可以让系统在运行时根据实际需要来**动态创建代理类,让同一个代理类能够代理多个不同的真实主题类而且可以代理不同的方法**
- Java语言提供了对动态代理的支持Java语言实现动态代理时需要用到位于**java.lang.reflect包**中的一些类
- Proxy类
- **public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)**:该方法用于返回一个**Class**类型的代理类,在参数中需要提供类加载器并需要指定代理的接口数组(与真实主题类的接口列表一致)
- **public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)**:该方法用于返回一个动态创建的代理类的实例,方法中第一个参数**loader**表示代理类的类加载器,第二个参数**interfaces**表示代理类所实现的接口列表(与真实主题类的接口列表一致),第三个参数**h**表示所指派的调用处理程序类
- InvocationHandler接口
- **InvocationHandler接口是代理处理程序类的实现接口**,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(**InvocationHandler**接口的子类)
- **public Object invoke(Object proxy, Method method, Object[] args)**:该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时将自动调用该方法。**invoke()**方法包含三个参数,其中第一个参数**proxy**表示代理类的实例,第二个参数**method**表示需要代理的方法,第三个参数**args**表示代理方法的参数数组
- 动态代理类需要在运行时指定所代理真实主题类的接口,客户端在调用动态代理对象的方法时,**调用请求会将请求自动转发给InvocationHandler对象的invoke()方法**由invoke()方法来实现对请求的统一处理。
> 动态代理实例
某软件公司欲为公司OA系统数据访问层DAO增加方法调用日志记录每一个方法被调用的时间和调用结果现使用动态代理进行设计和实现。
实例代码:
- AbstractUserDAO抽象用户DAO类抽象主题角色
- AbstractDocumentDAO抽象文档DAO类抽象主题角色
- UserDAO用户DAO类具体主题角色
- DocumentDAO文档DAO类具体主题角色
- DAOLogHandler自定义请求处理程序类
- Client客户端测试类
## 代理模式的优缺点与适用环境
> 模式优点
- 能够**协调调用者和被调用者**,在一定程度上降低了系统的耦合度
- 客户端可以针对抽象主题角色进行编程,**增加和更换代理类无须修改源代码,符合开闭原则**,系统具有较好的灵活性和可扩展性
- 逐个分析
- **远程代理**:可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,**提高了系统的整体运行效率**
- **虚拟代理**:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,**可以在一定程度上节省系统的运行开销**
- **缓冲代理**:为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,**优化系统性能,缩短执行时间**
- **保护代理****可以控制对一个对象的访问权限**,为不同用户提供不同级别的使用权限
> 模式缺点
- 由于在客户端和真实主题之间增加了代理对象,因此**有些类型的代理模式可能会造成请求的处理速度变慢**(例如**保护代理**
- 实现代理模式需要额外的工作,而且**有些代理模式的实现过程较为复杂**(例如**远程代理**
> 模式适用环境
- 当客户端对象需要访问远程主机中的对象时可以使用**远程代理**
- 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用**虚拟代理**
- 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用**缓冲代理**
- 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用**保护代理**
- 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用**智能引用代理**

@ -0,0 +1,242 @@
## 行为型模式概述
- **行为型模式(Behavioral Pattern) 关注系统中对象之间的交互**,研究系统在运行时**对象之间的相互通信与协作**,进一步**明确对象的职责**
- 行为型模式:**不仅仅关注类和对象本身**,还**重点关注它们之间的相互作用和职责划分**
> 类行为型模式
使用**继承关系**在几个类之间分配行为,主要**通过多态等方式来分配父类与子类的职责**
> 对象行为型模式
使用对象的**关联关系**来分配行为,主要**通过对象关联等方式来分配两个或多个类的职责**
> 行为型模式一览表
| **模式名称** | **定** **义** | **学习难度** | **使用频率** |
| ----------------------------------------------- | ------------------------------------------------------------ | ------------ | ------------ |
| **职责链模式(Chain of Responsibility Pattern)** | **避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。** | ★★★☆☆ | ★★☆☆☆ |
| **命令模式(Command Pattern)** | **将一个请求封装为一个对象,从而让你可以用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。** | ★★★☆☆ | ★★★★☆ |
| **解释器模式(Interpreter Pattern)** | **给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。** | ★★★★★ | ★☆☆☆☆ |
| **迭代器模式(IteratorPattern)** | **提供一种方法顺序访问一个聚合对象中的各个元素,且不用暴露该对象的内部表示。** | ★★★☆☆ | ★★★★★ |
| **中介者模式(Mediator Pattern)** | **定义一个对象来封装一系列对象的交互。中介者模式使各对象之间不需要显式地相互引用,从而使其耦合松散,而且让你可以独立地改变它们之间的交互。** | ★★★☆☆ | ★★☆☆☆ |
| **备忘录模式(Memento Pattern)** | **在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。** | ★★☆☆☆ | ★★☆☆☆ |
| **观察者模式(Observer Pattern)** | **定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都得到通知并被自动更新。** | ★★★☆☆ | ★★★★★ |
| **状态模式(State Pattern)** | **允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。** | ★★★☆☆ | ★★★☆☆ |
| **策略模式(Strategy Pattern)** | **定义一系列算法,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法可以独立于使用它的客户变化。** | ★☆☆☆☆ | ★★★★☆ |
| **模板方法模式(Template Method Pattern)** | **定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类不改变一个算法的结构即可重定义该算法的某些特定步骤。** | ★★☆☆☆ | ★★★☆☆ |
| **访问者模式(Visitor Pattern)** | **表示一个作用于某对象结构中的各个元素的操作。访问者模式让你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。** | ★★★★☆ | ★☆☆☆☆ |
## 职责链模式概述
> 奖学金审批示意图
![image-20211206081015184](设计模式/image-20211206081015184.png)
> 分析
- 辅导员、系主任、院长、校长都可以处理奖学金申请表,他们构成一个处理申请表的**链式结构****申请表沿着这条链进行传递**,这条链就称为**职责链**
- 职责链可以是**一条直线、一个环或者一个树形结构**,最常见的职责链是**直线型**,即沿着一条单向的链来传递请求
> 职责链模式的定义
**职责链模式****避免**将一个请求的发送者与接收者**耦合**在一起,**让多个对象都有机会处理请求**。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。
**Chain of Responsibility Pattern**: Avoid coupling the sender of a request to its receiver by giving **more than one object a chance to handle the request**. Chain the receiving objects and pass the request along the chain until an object handles it.
- **对象行为型**模式
- 将**请求的处理者组织成一条链**,并**让请求沿着链传递**,由链上的处理者对请求进行相应的处理
- **客户端无须关心请求的处理细节以及请求的传递**,只需将请求发送到链上,将**请求的发送者和请求的处理者解耦**
## 职责链模式的结构与实现
> 职责链模式的结构
![image-20211206081658896](设计模式/image-20211206081658896.png)
> 职责链模式包含以下两个角色:
- Handler抽象处理者
- ConcreteHandler具体处理者
> 职责链模式的实现
- 典型的**抽象处理者**代码
```java
public abstract class Handler {
//维持对下家的引用
protected Handler successor;
public void setSuccessor(Handler successor) {
this.successor=successor;
}
public abstract void handleRequest(String request);
}
```
- 典型的**具体处理者**代码
```java
public class ConcreteHandler extends Handler {
public void handleRequest(String request) {
if (请求满足条件) {
//处理请求
}
else {
this.successor.handleRequest(request); //转发请求
}
}
}
```
- 典型的**客户端**代码
```java
// ……
Handler handler1, handler2, handler3;
handler1 = new ConcreteHandlerA();
handler2 = new ConcreteHandlerB();
handler3 = new ConcreteHandlerC();
//创建职责链
handler1.setSuccessor(handler2);
handler2.setSuccessor(handler3);
//发送请求,请求对象通常为自定义类型
handler1.handleRequest("请求对象");
// ……
```
## 职责链模式的应用实例
> 实例说明
某企业的SCM(Supply Chain Management供应链管理)系统中包含一个采购审批子系统。该企业的采购审批是分级进行的即根据采购金额的不同由不同层次的主管人员来审批主任可以审批5万元以下不包括5万元的采购单副董事长可以审批5万元至10万元不包括10万元的采购单董事长可以审批10万元至50万元不包括50万元的采购单50万元及以上的采购单就需要开董事会讨论决定。如下图所示
![image-20211206082234097](设计模式/image-20211206082234097.png)
> 实例类图
![image-20211206082300273](设计模式/image-20211206082300273.png)
> 实例代码
- PurchaseRequest采购单类充当请求类
- Approver审批者类充当抽象处理者
- Director主任类充当具体处理者
- VicePresident副董事长类充当具体处理者
- President董事长类充当具体处理者
- Congress董事会类充当具体处理者
- Client客户端测试类
> 结果与分析
- 增加一个经理Manager角色
- 主任张无忌审批采购单10001金额45000.0元,采购目的:购买倚天剑。
- 经理黄蓉审批采购单10002金额60000.0元,采购目的:购买《葵花宝典》。
- 董事长郭靖审批采购单10003金额160000.0元,采购目的:购买《金刚经》。
- 召开董事会审批采购单10004金额800000.0元,采购目的:购买桃花岛。
```java
package designpatterns.cor;
//经理类:具体处理者
public class Manager extends Approver {
public Manager(String name) {
super(name);
}
//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 80000) {
System.out.println("经理" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); //处理请求
}
else {
this.successor.processRequest(request); //转发请求
}
}
}
```
客户端代码
```java
Approver rhuang;
rhuang = new Manager("黄蓉");
//创建职责链
wjzhang.setSuccessor(rhuang); //将“黄蓉”作为“张无忌”的下家
rhuang.setSuccessor(gyang); //将“杨过”作为“黄蓉”的下家
gyang.setSuccessor(jguo);
jguo.setSuccessor(meeting);
```
## 纯与不纯的职责链模式
> 纯的职责链模式
- 一个具体处理者对象只能在两个行为中选择一个:**要么承担全部责任,要么将责任推给下家**
- **不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递**的情况
- **一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理**的情况
> 不纯的职责链模式
- **允许某个请求被一个具体处理者部分处理后向下传递**,或者**一个具体处理者处理完某请求后其后继处理者可以继续处理该请求**
- 一个请求**可以最终不被任何处理者对象所接收并处理**
- JavaScript的**事件浮升(Event Bubbling)处理机制**
![image-20211206083341217](设计模式/image-20211206083341217.png)
## 职责链模式的优缺点与适用环境
> 模式优点
- 使得一个对象无须知道是其他哪一个对象处理其请求,**降低了系统的耦合度**
- **可简化对象之间的相互连接**
- 给对象**职责的分配带来更多的灵活性**
- **增加一个新的具体请求处理者时无须修改原有系统的代码**,只需要在客户端重新建链即可
> 模式缺点
- **不能保证请求一定会被处理**
- 对于比较长的职责链,**系统性能将受到一定影响,在进行代码调试时不太方便**
- 如果建链不当,可能会造成**循环调用****将导致系统陷入死循环(StackOverFlow)**
> 模式适用环境
- **有多个对象可以处理同一个请求**,具体哪个对象处理该请求**待运行时刻再确定**
- 在不明确指定接收者的情况下,**向多个对象中的一个提交一个请求**
- **可动态指定一组对象处理请求**
```
<html class="x-admin-sm">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>一个简单的登陆界面</title>
<link rel="shortcut icon" href="https://view6view.oss-cn-shanghai.aliyuncs.com/mylib/image-source/favicon.ico">
<link rel="stylesheet" href="./css/login.css">
<script src="./js/login.js"></script>
</head>
<body class="login-bg">
<div class="login">
<div class="message">一个简单的登陆界面</div>
<div id="darkbannerwrap"></div>
<form class="form" action="/login" method="post" target="_self" onsubmit="return mySubmit(this)">
<input name="username" placeholder="用户名" type="text" class="input">
<hr class="hr15">
<input name="password" placeholder="密码" type="password" class="input">
<hr class="hr15">
<input value="登录" style="width:100%;" type="submit">
<hr class="hr20" >
</form>
</div>
</body>
</html>
```

@ -0,0 +1,270 @@
## 命令模式概述
> 开关与电灯、排气扇示意图
![image-20211208143605884](设计模式/image-20211208143605884.png)
> 分析
- 现实生活
- **相同的开关**可以通过**不同的电线**来控制**不同的电器**
- **开关** <——> **请求发送者**
- **电灯** <——> **请求的最终接收者和处理者**
- 开关和电灯之间**并不存在直接耦合关系**,它们通过**电线**连接在一起,使用**不同的电线**可以**连接不同的请求接收者**
- 软件开发
- **按钮** <——> **请求发送者**
- **事件处理类** <——> **请求的最终接收者和处理者**
- 发送者与接收者之间引入了新的**命令对象**(类似电线),**将发送者的请求封装在命令对象中,再通过命令对象来调用接收者的方法**
- **相同的按钮**可以对应**不同的事件处理类**
> 动机
- 将请求发送者和接收者**完全解耦**
- 发送者与接收者之间**没有直接引用关系**
- 发送请求的对象**只需要知道如何发送请求,而不必知道如何完成请求**
> 命令模式的定义
**命令模式****将一个请求封装为一个对象**,从而让你可以用不同的请求**对客户进行参数化,对请求排队**或者**记录请求日志**,以及**支持可撤销的操作。**
**Command Pattern**: **Encapsulate a request as an object**, thereby letting you **parameterize clients** with different requests, **queue or log requests**, and **support undoable operations.**
- **对象行为型**模式
- 别名为**动作(Action)**模式或**事务(Transaction)**模式
- “用不同的请求对客户进行参数化”
- “对请求排队”
- “记录请求日志”
- “支持可撤销操作”
## 命令模式的结构与实现
> 命令模式的结构
![image-20211208144413985](设计模式/image-20211208144413985.png)
> 命令模式包含以下4个角色
- Command抽象命令类
- ConcreteCommand具体命令类
- Invoker调用者
- Receiver接收者
> 命令模式的实现
- 命令模式的本质是**对请求进行封装**
- **一个请求对应于一个命令**,将发出命令的责任和执行命令的责任分开
- 命令模式**允许请求的一方和接收的一方独立开来**,使得**请求的一方不必知道接收请求的一方的接口**,更不必知道请**求如何被接收、操作是否被执行、何时被执行**,以及**是怎么被执行的**
- 典型的**抽象命令类**代码:
```java
public abstract class Command {
public abstract void execute();
}
```
- 典型的**调用者(请求发送者)类**代码:
```java
public class Invoker {
private Command command;
//构造注入
public Invoker(Command command) {
this.command = command;
}
//设值注入
public void setCommand(Command command) {
this.command = command;
}
//业务方法用于调用命令类的execute()方法
public void call() {
command.execute();
}
}
```
- 典型的**具体命令类**代码:
```java
public class ConcreteCommand extends Command {
private Receiver receiver; //维持一个对请求接收者对象的引用
public void execute() {
receiver.action(); //调用请求接收者的业务处理方法action()
}
}
```
- 典型的**具体接收者**的代码:
```java
public class Receiver {
public void action() {
//具体操作
}
}
```
## 命令模式的应用实例
> 实例说明
为了用户使用方便某系统提供了一系列功能键用户可以自定义功能键的功能例如功能键FunctionButton可以用于退出系统由SystemExitClass类来实现也可以用于显示帮助文档由DisplayHelpClass类来实现
用户可以通过修改配置文件来改变功能键的用途,现使用命令模式来设计该系统,使得功能键类与功能类之间解耦,可为同一个功能键设置不同的功能。
> 实例类图
![image-20211208145239627](设计模式/image-20211208145239627.png)
> 实例代码
- FunctionButton功能键类充当请求调用者请求发送者
- Command抽象命令类
- ExitCommand退出命令类充当具体命令类
- HelpCommand帮助命令类充当具体命令类
- SystemExitClass退出系统模拟实现类充当请求接收者
- DisplayHelpClass显示帮助文档模拟实现类充当请求接收者
- Client客户端测试类
> 结果及分析
- 如果需要更换具体命令类,无须修改源代码,**只需修改配置文件****完全符合开闭原则**
- 每一个**具体命令类**对应一个**请求的处理者**(接收者),通过向请求发送者注入不同的具体命令对象可以使相同的发送者对应不同的接收者,从而实现“**将一个请求封装为一个对象,用不同的请求对客户进行参数化**”,**客户端只需要将具体命令对象作为参数注入请求发送者,无须直接操作请求的接收者**
- 配置文件
```xml
<?xml version="1.0"?>
<config>
<className>designpatterns.command.ExitCommand</className>
</config>
```
## 实现命令队列
> 动机
- 当一个请求发送者发送一个请求时,有**不止一个请求接收者产生响应**,这些请求接收者将逐个执行业务方法,完成对请求的处理
- 增加一个**CommandQueue类**,由该类负责**存储多个命令对象**,而不同的命令对象可以对应不同的请求接收者
- 批处理
> 实现
命令队列
```java
import java.util.*;
 
public class CommandQueue {
//定义一个ArrayList来存储命令队列
private ArrayList<Command> commands = new ArrayList<Command>();
public void addCommand(Command command) {
commands.add(command);
}
public void removeCommand(Command command) {
commands.remove(command);
}
//循环调用每一个命令对象的execute()方法
public void execute() {
for (Object command : commands) {
((Command)command).execute();
}
}
}
```
调用者
```java
public class Invoker {
//维持一个CommandQueue对象的引用
private CommandQueue commandQueue;
//构造注入
public Invoker(CommandQueue commandQueue) {
this. commandQueue = commandQueue;
}
//设值注入
public void setCommandQueue (CommandQueue commandQueue) {
this.commandQueue = commandQueue;
}
//调用CommandQueue类的execute()方法
public void call() {
commandQueue.execute();
}
}
```
## 记录请求日志
> 动机
- **将请求的历史记录保存下来**,通常以**日志文件(Log File)**的形式永久存储在计算机中
- 为系统提供一种**恢复机制**
- 可以用于实现**批处理**
- **防止因为断电或者系统重启等原因造成请求丢失**,而且可以**避免重新发送全部请求时造成某些命令的重复执行**
> 实现
- 将发送请求的命令对象**通过序列化写到日志文件**中
- 命令类必须实现接口**Serializable**
![image-20211208150517072](设计模式/image-20211208150517072.png)
## 实现撤销操作
> 实例
- 可以通过对命令类进行修改使得系统支持**撤销(Undo)操作**和**恢复(Redo)操作**
设计一个简易计算器,该计算器可以实现简单的数学运算,还可以对运算实施撤销操作。
> 结构
![image-20211208150834652](设计模式/image-20211208150834652.png)
> 实现
- 加法类Adder请求接收者
- 抽象命令类AbstractCommand
- 加法命令类AddCommand具体命令类
- 计算器界面类CalculatorForm请求发送者
- 客户端测试类Client
## 宏命令
> 结构
![image-20211208151026553](设计模式/image-20211208151026553.png)
## 命令模式的优缺点与适用环境
> 模式优点
- **降低系统的耦合度**
- 新的命令可以很容易地加入到系统中,**符合开闭原则**
- 可以比较容易地设计一个**命令队列或宏命令(组合命令)**
- 为请求的**撤销(Undo)和恢复(Redo)**操作提供了一种设计和实现方案
> 模式缺点
- 使用命令模式**可能会导致某些系统有过多的具体命令类**(针对每一个对请求接收者的调用操作都需要设计一个具体命令类)
> 模式适用环境
- 系统需要将**请求调用者和请求接收者解耦**,使得调用者和接收者不直接交互
- 系统需要**在不同的时间指定请求、将请求排队和执行请求**
- 系统需要**支持命令的撤销(Undo)操作和恢复(Redo)操作**
- 系统需要**将一组操作组合在一起形成宏命令**

@ -13,3 +13,21 @@
* [✍ 黑盒测试](src/university/103 "黑盒测试")
* [✍ 白盒测试](src/university/104 "白盒测试")
* [✍ Selenium使用教程—Java](src/university/105 "Selenium使用教程—Java")
* 🏁 设计模式
* [✍ 设计模式概述](src/university/201 "设计模式概述")
* [✍ 面向对象设计原则](src/university/202 "面向对象设计原则")
* [✍ 简单工厂模式](src/university/203 "简单工厂模式")
* [✍ 工厂方法模式](src/university/204 "工厂方法模式")
* [✍ 抽象工厂模式](src/university/205 "抽象工厂模式")
* [✍ 建造者模式](src/university/206 "建造者模式")
* [✍ 原型模式](src/university/207 "原型模式")
* [✍ 单例模式](src/university/208 "单例模式")
* [✍ 适配器模式](src/university/209 "适配器模式")
* [✍ 桥接模式](src/university/210 "桥接模式")
* [✍ 组合模式](src/university/211 "组合模式")
* [✍ 装饰模式](src/university/212 "装饰模式")
* [✍ 外观模式](src/university/213 "外观模式")
* [✍ 享元模式](src/university/214 "享元模式")
* [✍ 代理模式](src/university/215 "代理模式")
* [✍ 职责链模式](src/university/216 "职责链模式")
* [✍ 命令模式](src/university/217 "命令模式")

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save