# 设计模式 **Repository Path**: zhunk/design-patterns ## Basic Information - **Project Name**: 设计模式 - **Description**: 23种设计模式,主要介绍各种涉及设计模式的原理,结构图,以及设计模式的应用实例和使用场景,还有其中的优缺点等 - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: https://gitee.com/AristoVBY/design-patterns - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2024-12-27 - **Last Updated**: 2024-12-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 设计模式 ## 介绍 > 23种设计模式 >### 包括各种涉及模式的定义,结构图,源码,以及优缺点的介绍 ## 思维导图 #### 项目说明 巩固自己的代码能力,因为不管什么时候,作为一名程序员,编程的技术功底才是我们的立身之本 越努力,越幸运!~~ #### 使用说明 ### 代理模式 *静态代理: * 优点: 在不修改目标类的前提下,扩展目标类的功能 * 缺点: 冗余 不易维护 ![img_cglib_proxy.png](img/img_proxy_cglib.png) ### 桥接模式 > 将抽象部分与实现部分进行分离,使他们可以独立的变化 > >具体实例 > ![img_2.png](img/img_proxy_appEg.png) #### 应用实例 > 主要应用不同的支付工具对应的不同的支付模式, 比如可以使用支付宝或者微信进行付款操作 我们们可以细分为 > 两个渠道: **支付渠道**和**支付方式** > ![img_3.png](img/img_proxy_wxAndZfb.png) > 支付渠道和支付方式UML图 > ![img_4.png](img/img_proxy_zfbwx.png) > 桥接模式的 > ## 优点 > 1.解耦抽象和实现间固有的绑定关系 > > 2.取代多层继承方案 > > 3.提高扩展性 > > ## 缺点 > 程序员使用此模式一定要识别出两个独立变化的维度, ## 装饰器模式 > 介绍 > ![img.png](img/img_dorcorator_doc.png) > 装饰器模式结构图 ![img.png](img/img_decorator_struct.png) > 我们用一个文件读写程序为例,演示一下装饰者模式的使用,下面是UML的类图 > > `DataLoader`:抽象的文件读取接口 > > `BaseFileDataLoader`:具体组件,重写`DataLOader`的读写方法 > > `DataLoaderDecorator`:抽象装饰类 > > `EncryptionDataDecorator`: 具体的装饰类,对具体的组件的扩展(读写加密功能的装饰类) > ![img.png](img/img_decorator_fileReadAndWrite_UML.png) # 适配器模式 ## 1.定义 ![img.png](img/img_adapter_concept.png) ## 2.类适配器模式结构图 ![img.png](img/img_adapter_class.png) ## 3.对象适配器模式 ![img.png](img/img_adapter_object.png) ## 4.类适配器模式应用实例 ![img_adapter_class_appEg.png](img/img_adapter_class_appEg.png) ![img_adapter_class_app_UML.png](img/img_adapter_class_app_UML.png) ## 对象适配器模式 ![img.png](img/img_adapter_object_UML.png) ## 适配器模式的优缺点 ### 1.优点 > 1.将目标类与适配者类解耦,引入一个适配器类来重用现有的适配者类,不用修改原来的结构 > > 2.增加类的透明性和复用性,将具体业务的实现过程封装在适配者类中,对于客户端是透明的,提高适配者的复用性,同一个适配者类可以再多个不同的系统中复用 > > 3.灵活性和扩展性好,符合开闭原则 ### 2.缺点 > 1.Java不支持多重继承,一次做多适配一个适配者类,不能同时适配多个适配者 > > 2.适配者类不能为最终类 > ### 适配的场景 ![img.png](img/img_adapter_scene.png) # 外观模式 Facade Pattern ## 1.定义 > 原始定义: > 为子系统的一组接口提供统一的接口,定义的是一个或更高级别的接口,目的为了让子系统更好用 > > 利用了迪米特法则和接口隔离原则====>两个有交互的系统,只是暴露有限的必要接口 ![img.png](img/img_facade_concept.png) ## 外观模式原理图 ![img.png](img/img_facade_theory.png) ![img.png](img/img_facade_useScene.png) # 组合模式 ## 定义 > ### 组合模式=数据结构+算法 > ![img.png](img/img_composite_concept.png) > ## 组合模式的结构图 ![img.png](img/img_composite_structUML.png) ## 组合模式应用实例 ![img.png](img/img_composite_appEgUML.png) ## 组合模式优缺点 ![img.png](img/img_composite_diff.png) # 享元模式 FlyWeight pattern ## 1定义 > 摒弃每个对象保存原有数据的方式,通过共享多个对象所共有的的相同状态,让我们在有限内存容量加载更多对象 > > 节约内存空间====> 共享空间 ## 享元模式结构图 ![img.png](img/img_flyweight_struct.png) ## 享元模式实际应用 ## 享元模式的总结 ![img.png](img/img_flyweight_summary.png) #### 参与贡献 1. Fork 本仓库 2. 新建 design_patterns 分支 3. 提交代码 4. 新建 Pull Request # 行为型模式 (11种) > 类行为模式 : 模板方法模式, 解释器模式 > > 对象行为模式 ![img.png](img/img_behavior_introduce.png) # 6.1观察者模式 ## 6.1.1 原始定义 > 定义对象之间一对多的依赖关系,这样当一个对象改变时,它的所有依赖项都会自动得到通知而自动更新 ![img.png](img/img_observer_pattern_introduce.png) ## 6.1.2 观察者模式原理 ![img.png](img/img_observer_principal.png) ## 6.1.3 观察者模式的优缺点 ![img.png](img/img_observer_pattern_meritsAndDemerits.png) # 6.2 模板方法模式 ## 6.2.1 定义 > 原始定义: 在操作中定义算法的框架,将一些步骤推迟到子类.模板方法让子类爱不改变算法结构的情况下重新定义算法的某些步骤 > > 算法: 可以认为是我们的业务逻辑 ## 6.2.2 模板方法模式的结构图 ![img.png](img/img_templatemethod_pattern_struct.png) ## 6.2.3 优缺点 ![img.png](img/img_theplatemethod_pattern_merits.png) # 6.3 策略模式 ## 6.3.1 定义 > 策略模式: 定义一系列算法,将每一个算法封装起来,不能够使他们可以相互的替换 > > 策略模式可以让算法独立于使用她的客户端变化 > 举个例子: ![img.png](img/img_strategy_pattern_concept.png) ## 6.3.2 策略模式结构图 ![img.png](img/img_strategy_pattern_struct.png) ## 6.3.3 ![img.png](img/img_strategy_pattern_merits.png) # 责任链模式 # 6.4 职责链模式 ## 6.4.1 原始定义: > 避免将一个请求的发送者和接受者耦合在一起,让多个对象都有机会处理请求,将接受请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止 ## 结构图 ![img.png](img/img_chainOfResponsibilityStruct.png) ## 应用实例 ![img.png](img/img_chainduty_pattern_application_example.png) ## 职责链模式总结 ![img.png](img/img_chainDuty_summary.png) # 6.5 状态模式 ### 状态模式的介绍 ![img.png](img/img_state_pattern_concept.png) # 状态模式的结构图 ![img.png](img/img_state_pattern_struct.png) ## 状态模式的总结 ![img.png](img/img_state_pattern_summary.png) # 迭代器模式 ![img.png](img/img_iterator_pattern_concept.png) ## 迭代器模式的结构图 ![img.png](img/img_iterator_pattern_struct.png) ## 迭代器模式优缺点&&使用场景 ![img.png](img/img_Iterator_pattern_merits.png) # 访问者模式 ## 定义 > 允许在运行的时候将一个或者多个操作应用于一组对象,将操作于对象结构分离 ## 访问者模式结构图 ![img.png](img/img_visitor_pattern_struct.png) ## 访问者模式的实现 访问者模式包含以下主要角色: * 抽象访问者(Visitor)角色:可以是接口或者抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定那个重载方法被调用. * 具体访问者(ConcreteVisitor)角色:访问者接口的实现类,可以有多个实现,每个访问者都需要实现所有数据元素类型的访问重载方法. * 抽象元素(Element)角色:被访问的数据元素接口,定义了一个接受访问者的方法(`accept`),其意义是指,每一个元素都要可以被访问者访问。 * 具体元素(ConcreteElement)角色: 具体数据元素实现类,提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法,其accept实现方法中调用访问者并将自己 "this" 传回。 * 对象结构(Object Structure)角色:包含所有可能被访问的数据对象的容器,可以提供数据对象的迭代功能,可以是任意类型的数据结构. * 客户端 ( Client ) : 使用容器并初始化其中各类数据元素,并选择合适的访问者处理容器中的所有数据对象. > ### 我们以超市购物为例,假设超市中的三类商品: 水果,糖果,酒水进行售卖. 我们可以忽略每种商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中添加计价方法是不合理的设计.我们先来定义糖果类和酒类、水果类. **访问者接口** - 收银员就类似于访问者,访问用户选择的商品,我们假设根据生产日期进行打折,过期商品不能够出售. 注意这种计价策略不适用于酒类,作为收银员要对不同商品应用不同的计价方法. **具体访问者** - 创建计价业务类,对三类商品进行折扣计价,折扣计价访问者的三个重载方法分别实现了3类商品的计价方法,体现了visit() 方法的多态性. ## 访问者模式的总结 **1) 访问者模式优点:** * 扩展性好 在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。 * 复用性好 通过访问者来定义整个对象结构通用的功能,从而提高复用程度。 * 分离无关行为 通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。 **2) 访问者模式缺点:** * 对象结构变化很困难 在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。 * 违反了依赖倒置原则 访问者模式依赖了具体类,而没有依赖抽象类。 **3) 使用场景** * 当对象的数据结构相对稳定,而操作却经常变化的时候。 比如,上面例子中路由器本身的内部构造(也就是数据结构)不会怎么变化,但是在不同操作系统下的操作可能会经常变化,比如,发送数据、接收数据等。 * 需要将数据结构与不常用的操作进行分离的时候。 比如,扫描文件内容这个动作通常不是文件常用的操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系(树形结构的遍历操作),扫描是一个额外的动作,如果给每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式是非常合适的,能够很好分离文件自身的遍历操作和外部的扫描操作。 * 需要在运行时动态决定使用哪些对象和方法的时候。 比如,对于监控系统来说,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和参数,这时使用访问者模式就可以动态增加监控行为。 # 6.8 备忘录模式 ### 6.8.1 备忘录模式介绍 备忘录模式提供了一种对象状态的撤销实现机制,当系统中某一个对象需要恢复到某一历史状态时可以使用备忘录模式进行设计. > 很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。 **备忘录模式(memento pattern)定义: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态.** ![img.png](img/img_memento_pattern_eg.png) ### 6.8.2 备忘录模式原理 ![img.png](img/img_memento_pattern_principple.png) ### 6.8.5 备忘录模式总结 **1 ) 备忘录模式的优点** 1. 提供了一种状态恢复的实现机制,使得用户可以方便的回到一个特定的历史步骤,当新的状态无效或者存在问题的时候,可以使用暂时存储起来的备忘录将状态复原. 2. 备忘录实现了对信息的封装,一个备忘录对象是一种发起者对象状态的表示,不会被其他代码所改动.备忘录保存了发起者的状态,采用集合来存储备忘录可以实现多次撤销的操作 **2 ) 备忘录模式的缺点** - 资源消耗过大,如果需要保存的发起者类的成员变量比较多, 就不可避免的需要占用大量的存储空间,每保存一次对象的状态,都需要消耗一定系统资源 **3) 备忘录模式使用场景** 1. 需要保存一个对象在某一时刻的状态时,可以使用备忘录模式. 2. 不希望外界直接访问对象内部状态时. ![img.png](img/img_memento_pattern_principle.png) 备忘录模式的主要角色如下: * 发起人(Originator)角色:状态需要被记录的元对象类, 记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。 * 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。 * 看护人(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。 # 命令模式 ## 定义 > **命令模式(command pattern)的定义: 命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不 同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等 (附加控制)功能。** 命令模式的核心是将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,达到使命令的请求与执行方解耦,双方只通过传递各种命令对象来完成任务. 在实际的开发中,如果你用到的编程语言并不支持用函数作为参数来传递,那么就可以借助命令模式将函数封装为对象来使用。 > 我们知道,C 语 言支持函数指针,我们可以把函数当作变量传递来传递去。但是,在大部分编程语言中,函 数没法儿作为参数传递给其他函数,也没法儿赋值给变量。借助命令模式,我们可以将函数 封装成对象。具体来说就是,设计一个包含这个函数的类,实例化一个对象传来传去,这样 就可以实现把函数像对象一样使用。 ## 原理图 ![img.png](img/img_command_pattern_principle.png) 命令模式包含以下主要角色: * 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。 * 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。 * 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。 * 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。 ## 命令模式总结 **1) 命令模式优点:** * 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。 * 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。 * 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。 **2) 命令模式缺点:** * 使用命令模式可能会导致某些系统有过多的具体命令类。 * 系统结构更加复杂。 **3) 使用场景** * 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。 * 系统需要在不同的时间指定请求、将请求排队和执行请求。 * 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。 # 解释器模式 ## 定义 解释器模式使用频率不算高,通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到,比如编译器、规则引擎、正则表达式、SQL 解析等。不过,了解它的实现原理同样很重要,能帮助你思考如何通过更简洁的规则来表示复杂的逻辑。 **解释器模式(Interpreter pattern)的原始定义是:用于定义语言的语法规则表示,并提供解释器来处理句子中的语法。** 我们通过一个例子给大家解释一下解释器模式 - 假设我们设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。 ```java //用于两个整数相加的方法 public static int add(int a , int b){ return a + b; } //用于三个整数相加的方法 public static int add(int a , int b,int c){ return a + b + c; } public static int add(Integer ... arr){ int sum = 0; for(Integer num : arr){ sum += num; } return sum; } + - ``` 上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如: 5-3+2-1, 10-5+20.... **文法规则和抽象语法树** 解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子. 在上面提到的加法/减法解释器中,每一个输入表达式(比如:2+3+4-5) 都包含了3个语言单位,可以使用下面的文法规则定义: 文法是用于描述语言的语法结构的形式规则。 ``` expression ::= value | plus | minus plus ::= expression ‘+’ expression minus ::= expression ‘-’ expression value ::= integer ``` > 注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。 上面规则描述为 : 表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。 **抽象语法树:** 在解释器模式中还可以通过一种称为抽象语法树的图形方式来直观的表示语言的构成,每一棵抽象语法树对应一个语言实例,例如加法/减法表达式语言中的语句 " 1+ 2 + 3 - 4 + 1" 可以通过下面的抽象语法树表示 ## 原理图 ![img.png](img/img_interprter_pattern_priciple01.png) 解释器模式包含以下主要角色。 * 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。 * 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。上例中的value 是终结符表达式. * 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。上例中的 plus , minus 都是非终结符表达式 * 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。 * 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。 ## 总结 优缺点呢 **1) 解释器优点** - 易于改变和扩展文法 因为在解释器模式中使用类来表示语言的文法规则的,因此就可以通过继承等机制改变或者扩展文法.每一个文法规则都可以表示为一个类,因此我们可以快速的实现一个迷你的语言 - 实现文法比较容易 在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂 - 增加新的解释表达式比较方便 如果用户需要增加新的解释表达式,只需要对应增加一个新的表达式类就可以了.原有的表达式类不需要修改,符合开闭原则 **2) 解释器缺点** - 对于复杂文法难以维护 在解释器中一条规则至少要定义一个类,因此一个语言中如果有太多的文法规则,就会使类的个数急剧增加,当值系统的维护难以管理. - 执行效率低 在解释器模式中大量的使用了循环和递归调用,所有复杂的句子执行起来,整个过程也是非常的繁琐 **3) 使用场景** - 当语言的文法比较简单,并且执行效率不是关键问题. - 当问题重复出现,且可以用一种简单的语言来进行表达 - 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象的语法树的时候 # 中介者模式 ### 中介者模式介绍 提到中介模式,有一个比较经典的例子就是航空管制。 为了让飞机在飞行的时候互不干扰,每架飞机都需要知道其他飞机每时每刻的位置,这就需要时刻跟其他飞机通信。飞机通信形成的通信网络就会无比复杂。这个时候,我们通过引 入“塔台”这样一个中介,让每架飞机只跟塔台来通信,发送自己的位置给塔台,由塔台来 负责每架飞机的航线调度。这样就大大简化了通信网络。 ![img.png](img/img_mediator_pattern_introduce.png) **中介模式(mediator pattern)的定义: 定义一个单独的(中介)对象,来封装一组对象之间的交互,将这组对象之间的交互委派给予中介对象交互,来避免对象之间的交互.** 中介者对象就是用于处理对象与对象之间的直接交互,封装了多个对象之间的交互细节。中介模式的设计跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系从多对多的网状关系转换为一对多的星状关系.原来一个对象要跟N个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低代码的复杂度,提高代码的可读性和可维护性. ## 原理图 ![img.png](img/img_mediator_pattern_principle.png) ## 优缺点 **1) 中介者模式的优点** - 中介者模式简化了对象之间的交互,他用中介者和同事的一对多代替了原来的同事之间的多对多的交互,一对多关系更好理解 易于维护和扩展,将原本难以理解的网状结构转换成习相对简单的星型结构. - 可以将各个同事就对象进行解耦.中介者有利于各个同事之间的松耦合,可以独立的改变或者复用每一个同事或者中介者,增加新的中介者类和新的同事类都比较方便,更符合开闭原则 - 可以减少子类生成,中介者将原本分布与多个对象的行为集中在了一起,改变这些行为只需要生成新的中介者的子类即可,使得同事类可以被重用,无需直接对同事类进行扩展. **2) 中介者模式的缺点** - 在具体中介者类中包含了大量同事之间的交互细节,可能会导致中介者类变得非常的复杂,使得系统不好维护. **3) 中介者模式使用场景** - 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解. - 一个对象由于引用了其他的很多对象并且直接和这些对象进行通信,导致难以复用该对象. - 想要通过一个中间类来封装多个类中的行为,而又不想生成太多的子类,此时可以通过引用中介者类来实现,在中介者类中定义对象的交互的公共行为,如果需要改变行为则可以在增加新的中介类. 1. 使用 Readme.md 来支持不同的语言, Readme\_en.md: 支持英文, Readme\_zh.md: 支持中文 2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) 3. 小的进步,才是大的开始!!! 4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) 6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)