# design-pattern-java **Repository Path**: jerris-code-space/design-pattern-java ## Basic Information - **Project Name**: design-pattern-java - **Description**: 设计模式 Java 实现教程,通过本项目你可以了解到 23 种设计模式的具体实现方式以及实际应用! - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 2 - **Created**: 2021-10-04 - **Last Updated**: 2025-04-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: 设计模式, 架构设计, 系统重构, 高可用, 解耦合 ## README # 项目介绍 本项目是一个Java设计模式的实战教程,包含了23中GoF设计模式的Java实现以及UML基础知识和软件设计的七大原则说明内容.适合初识设计模式的程序员学习.通过本项目,你可以了解到如何进行架构设计,代码解耦合,系统重构等思想. # 一.UML ## 1.1 UML的定义 ​ 统一建模语言(Unified Modeling Language, UML)是一种面向对象系统的产品进行说明,可视化和编制文档的标准语言,是非专利的第三代建模和规约语言.UML是一种面向对象设计的建模工具,是在开发阶段说明,可视化,构建和书写一个面向对象软件密集系统的制品的开放方法,但独立于任何具体的程序设计语言. ## 1.2 UML应用场景 ​ UML最佳应用是工程实践,在对大规模,复杂系统进行建模方面,特别是软件架构层次,已经被验证有效,UML模型大多以图表的方式表现出来.一份典型的建模图表通常包含几个块或框,连接线和作为模型附加信息的文本.这些虽简单却非常重要,在UML规则中相互联系和扩展. ## 1.3 UML基本构件 ​ UML建模的核心是模型,模型是实现的简化,真实系统的抽象.UML提供了系统的设计蓝图.当给软件系统建模时,需要采用通用的符号语言,这种描述模型所使用的语言被称为建模语言.在UML中,所有的描述由事务,关系和图这些构件组成. ​ ![](doc/asset/UML.png) ### 1.3.1 事物 ​ 事物是抽象化的最终结果,分为结构事物,行为事物,分组事物和注释事物. #### 1) 结构事物 ​ 结构事物是模型中的静态部分,用以呈现概念或实体的表现元素 | 事物 | 解释 | 图例 | | -------------------- | ------------------------------------------------------------ | ------------------------------ | | 类(Class) | 具有相同属性,方法,关系和语义的对象集合 | ![](doc/asset/class.png) | | 接口(Interface) | 指一个类或构件的一个服务的操作集合,它仅仅定义了一组操作规范,并没有给出这组操作的具体实现 | ![](doc/asset/interface.png) | | 用例(User Case) | 指对一组动作序列的描述,系统执行这些动作将产生一个对特定的参与者(Actor)有价值且可观察的结果 | ![](doc/asset/User-Case.png) | | 协作(Collaborations) | 定义元素之间的相互作用 | ![](doc/asset/Collaborations.png) | | 组件(Component) | 描述物理系统的一部分 | ![](doc/asset/Component.png) | | 活动类(Active Class) | 指对象有一个或多个进程或线程.活动类和类很相像,只是它的对象代表的元素行为和其他元素是同时存在的 | ![](doc/asset/ActiveClass.png) | | 节点(Node) | 定义为运行时存在的物理元素 | ![](doc/asset/Node.png) | #### 2) 行为事物 ​ 行为事物是指UML建模中的动态部分. | 事物 | 解释 | 图例 | | --------------------- | -------------------------- | ---------------------------- | | 交互(Interaction) | 包括一组元素之间的消息交换 | ![](doc/asset/Interaction.png) | | 状态机(State Machine) | 由一系列对象的状态组成 | ![](doc/asset/StateMachine.png) | #### 3) 分组事物 ​ 目前只有一种分组事物,即包.包纯粹是概念上的,只存在于开发阶段,结构事物,行为事物甚至分组事物都有可能放在一个包中. | 事物 | 解释 | 图例 | | ----------- | ------------------- | ----------------------- | | 包(Package) | UML中唯一的组织机制 | ![](doc/asset/Package.png) | #### 4) 注释事物 ​ 注释事物是解释UML模型元素的部分. | 事物 | 解释 | 图例 | | ---- | ------------------- | -------------------- | | 注释 | 用于解析说明UML元素 | ![](doc/asset/Note.png) | ### 1.3.2 关系 UML将事物之间的联系归纳为6种,并用对应的图形类表示. | 事物关系 | 说明 | 图例 | | -------------------- | ------------------------------------------------------------ | ------------------------------ | | 关联(Association) | 表示一种拥有的关系,具有方向性.如果一
个类单方向地访问另一个类,则称为单向
关联;如果两个类的想可以互相访问,则称为
双向关联,一个对象能访问关联对象的数目叫作"多重性". | ![](doc/asset/Assocation.png) | | 聚合(Aggregate) | 表示整体与部分的关系.当某个实体聚合成
另一个实体时,该实体还可以是另一个实体的部分. | ![](doc/asset/Aggregate.png) | | 组合(Combination) | 表示整体与部分的关系,组合比聚合
更加严格.当某个实体组合成另一个
实体时,二者具有相同的生命周期,例如
手臂和人之间是组合关系. | ![](doc/asset/Combination.png) | | 泛化(Generalization) | 表示一个更泛化的元素与一个更具体的元素
之间的关系,与继承是同一个概念. | ![](doc/asset/Generalization.png) | | 实现(Realization) | 表示类与接口的关系,类实现接口. | ![](doc/asset/Realization.png) | | 依赖(Dependency) | 如果一个类的改动会影响另一个
类,则两个类之间存在依赖关系,一般而言,依赖是单向的. | ![](doc/asset/Dependency.png) | ### 1.3.3 图 UML2.0一共有13种图(UML1.5定义了9种,UML2.0增加了4种),分别是类图,对象图,构件图,部署图,活动图,状态图,用例图,时序图,协作图9种,以及包图,组合结构图,时间图,交互概览图4种. | 图名称 | 解释 | | ----------------------------------------- | ------------------------------------------------------------ | | 类图(Class Diagrams) | 用于定义系统种的类 | | 对象图(Object Diagrams) | 类图的一个实例,描述了系统在具体时间点上所包含的对象及各个对象之间的关系 | | 构件图(Component Diagrams) | 一种特殊的UML图,描述系统的静态实现视图 | | 部署图(Deployment Diagrams) | 定义系统中软硬件的物理体系结构 | | 活动图(Activity Diagrams) | 用来描述满足用例要求所要进行的活动及活动时间的约束关系 | | 状态图(State Chart Diagrams) | 用来描述类的对象的所有可能的状态和时间发生时,状态的转移条件 | | 用例图(Usecase Diagrams) | 用来描述用户的需求,从用户的角度描述系统的功能,并指出各功能的执行者,强调谁在使用系统,系统为执行者完成哪些功能 | | 时序图(Sequence Diagrams) | 描述对象之间的交互顺序,着重体现对象间消息传递的时间顺序,强调对象之间消息的发送顺序,同时显示对象之间的交互过程 | | 协作图(Collaboration Diagrams) | 描述对象之间的合作关系,更侧重向用户对象说明哪些对象有消息的传递 | | 包图(Package Diagrams) | 对构成系统的模型元素进行分组整理的图 | | 组合结构图(Composite Structure Diagrams) | 表示类或者构建内部结构的图 | | 时间图(Timing Diagrams) | 用来显示随时间变化,一个或多个元素的值或状态的更改,也显示时间控制事件之间的交互及管理它们的时间和期限约束 | | 交互概览图(Interaction Overview Diagrams) | 用活动图来表示多个交互之间的控制关系的图 | # 二.设计模式常用UML图 ## 2.1 类图 ​ 在UML 2.0的13种图中,类图(Class Diagrams)是使用频率最高的UML图之一.类图描述系统中的类,以及各个类之间的关系的静态视图,能够让我们在正确编写代码之前对系统有一个全面的认识.类图是一种模型类型,确切地说,是一种静态模型类型.类图表示类,接口和它们之间的协作关系,用于系统设计阶段. ### 2.1.1 继承关系 ​ 在继承(Generalization,又叫作泛化)关系中,子类继承父类的所有的功能,父类所具有的属性,方法,子类都应该有.除了与父类一致的信息,子类中还包括额外的信息. ### 2.1.2 实现关系 ​ 接口(包括抽象类)是方法的集合,在实现(Realization)关系中,类实现了接口,类中的方法实现了接口声明的所有方法. ### 2.1.3 组合关系 ​ 组合(Combination)关系表示类之间整体与部分的关系,整体与部分有一致的生存期.一旦整体对象不存在,部分对象也将不存在,整体和部分是同生共死的关系. ### 2.1.4 聚合关系 ​ 聚合(Aggregate)关系表示类之间整体与部分的关系,成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在.例如,公交车司机和工作服,工作帽是整体与部分的关系,但是可以分开,没有共同的声明周期.工作服,工作帽可以穿,戴在别的司机身上,公交车司机也可以换别人的工作服,工作帽. ### 2.1.5 关联关系 ​ 关联(Association)关系是类与类之间最常用的一种关系,表示一类对象与另一类对象之间有联系.组合,聚合也属于关联关系只是关联关系的类间关系比其他两种关系要弱. ​ 关联关系有4种,双向关联,单向关联,自关联,多重性关联.例如汽车和司机,一辆汽车对应特定的司机,一个司机也可以开多辆车. ​ 在多重性关联关系中,可以直接在关联直线上增加一个数字,表示与之对应的另一个类的对象的个数,具体含义如下表所示: | 表示方式 | 含义 | | -------- | ------------------- | | 1..1 | 仅一个 | | 0..* | 零个或多个 | | 1..* | 一个或多个 | | 0..1 | 没有或只有一个 | | m..n | 最少m,最多n个(m<=n) | ### 2.1.6 依赖关系 ​ 依赖(Dependency)关系是一种"使用"关系,特定事物的改变有可能会影响到使用该事物的其他事物,当需要表示一个事物使用另一个事物时,使用依赖关系.在大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数.例如: 汽车依赖汽油,如果没有汽油,则汽车无法行驶. ​ 在这6种关系中,组合,聚合和关联的代码结构一样,可以从关系的强弱来理解,各类关系从强到弱依次是: 继承 > 实现 > 组合 > 聚合 > 关联 > 依赖. ### 2.1.7 类关系记忆技巧 | 分类 | 箭头特征 | 记忆技巧 | | --------- | -------------------------------------------- | ------------------------------------------------------------ | | 箭头方向 | 从子类指向父类 | 1. 定义子类需要通过extends关键字指定父类
2. 子类一定是知道父类的定义的,但父类并不知道子类的定义
3. 只有知道对方信息时才能指向对方
4. 箭头的方向是从子类指向父类 | | 继承/实现 | 用线条连接两个类空心三角形箭头表示继承或实现 | 实线表示继承,是is-a的关系,表示扩展,不虚,很结实
虚线表示实现,虚线代表"虚"无实体 | | 关联/依赖 | 用线条连接两个类;
普通箭头表示关联或依赖 | 1. 虚线表示依赖关系,临时用一下,若即若离,虚无缥缈,若有若无
2. 表示一种使用关系,一个类需要借助另一个类来实现功能
3. 一般一个类将另一个类作为参数使用,或作为返回值
4. 实线表示关联关系,关系稳定,实打实的关系,"铁哥们"
5. 表示一个类对象和另一个类对象有关联
6. 通常一个类中有另一个类对象作为属性 | | 组合/聚合 | 用菱形表示;
像一个盛东西的器皿 | 1. 聚合:空心菱形,代表空器皿里可以放很多相同的东西,聚集在一起(箭头方向所指的类)
2. 整体和局部的关系,两者有独立的声明周期,是has-a的关系
3. 弱关系,消极的词,弱-空
4. 组合,实心菱形,代表器皿已经有实体结构的存在,生死与共
5. 整体与局部的关系,和聚合关系对比,关系更加强烈,两者具有相同的生命周期,contains-a的关系
6. 强关系,积极的词,强-满. | ## 2.2 时序图 ​ 时序图(Sequence Diagrams)描述对象之间消息的发送顺序,强调时间顺序.时序图是一个二维图,横轴表示对象,纵横表示时间,消息在各对象之间横向传递,按照时间顺序纵向排列,用箭头表示消息,用竖虚线表示对象生命线. ### 2.2.1 时序图的作用 ​ (1) 展示对象之间交互的顺序.将交互行为建模为消息的传递,通过描述消息如何在对象间发送和接收来动态展示对象之间的交互. ​ (2) 相对于其他的UML图,时序图更强调交互的时间顺序. ​ (3) 可以直观地描述并发进程. ### 2.2.2 时序图组成元素 ​ 时序图组成元素主要包括角色(Actor),对象(Object),生命线(Lifeline),控制焦点(Focus of Control)和消息(Message),其具体解释如下表所示: | 元素 | 解释 | 图例 | | -------- | ------------------------------------------------------------ | ------------------------------ | | 角色 | 系统角色,可以是人,机器,其他系统,子系统 | ![](doc/asset/role.png) | | 对象 | 1. 对象的三种命名方式:
第一种方式包括对象名和类名
第二种方式只显示类名,即表示它是一个匿名对象
第三种方式只显示对象名不显示类名
2. 命名方式的选择
三种命名方式均可,那种最容易让阅读该时序图的人理解,就选哪种
3. 对象的排列顺序
对象的左右顺序并不重要,但是为了作图清晰整洁,通常应遵循以下两个原则,把交 互频繁地对象尽可能靠拢,把初始化整个交互活动的对象放置在最左侧 | ![](doc/asset/Object.png) | | 生命线 | 在时序图中表示为从对象图标向下延伸的一条虚线,表示对象存在的时间 | ![](doc/asset/life-line.png) | | 控制焦点 | 又被称为激活期,表示时间段的符号,表示在这个时间段内对象将执行相应的操作可以理解为Java中一对大括号中的内容 | ![](doc/asset/FocusOfControl.png) | | 消息 | 消息一般分为同步消息(Synchronous Message),异步消息(Asynchronous Message)和返回消息(Return Message)
1. 消息发送者把控制传递给消息接收者,然后停止活动,等待消息接收者放弃或者返回控制,用来表示同步的意义.
2. 消息发送者通过消息把信号传递给消息接收者,然后继续自己的活动,不等待接收者返回消息或者控制.异步消息的接收者和发送者是并发工作的.
3. 返回消息表示从过程调用返回 | ![](doc/asset/Interaction.png) | ### 2.2.3 时序图组合片段 ​ 组合片段(Combined Fragments)用来解决交互执行的条件和方式.它允许在时序图中直接表示逻辑组件,用于通过指定条件或子进程的应用区域,为任何生命线的任何部分定义特殊条件和子进程.组合片段共有13种,名称及含义如下: | 类型 | 名称 | 说明 | | -------- | :----- | ------------------------------------------------------------ | | Alt | 抉择 | 包含一个片段列表,这些片段包含备选消息序列.在任何场合下只发生一个序列.可以在每个片段中都设置一个临界来指示该片段可以运行的条件.else的临界指示其他临界都不为true时应运行的片段.如果所有临界都为false并且没有else,则不执行任何片段. | | Opt | 选项 | 包含一个可能发生或不可能发生的序列.可以在临界中指定序列发生的条件. | | Loop | 循环 | 片段重复一定次数.可以在临界中指示片段重复的条件.Loop组合片段具有Min和Max属性,它们指示片段可以重复的最小值和最大次数.默认值是无限制. | | Break | 中断 | 如果执行此片段,则放弃序列的其余部分.可以使用临界来指示发生中断的条件. | | Par | 并行 | 并行处理.片段中的事件可以交错. | | Critical | 关键 | 用来Par或Seq片段中.指示此片段中的消息不得与其他消息交错. | | Seq | 弱顺序 | 有两个或更多操作数片段.涉及同一生命线的消息必须按片段的顺序发生.如果消息涉及的生命线不同,则来自不同片段的消息可能会并行交错. | | Strict | 强顺序 | 有两个或更多操作数片段,这些片段必须按给定顺序发生. | | Consider | 考虑 | 指定此片段描述的消息列表.其他消息可发生在运行的系统中,但对此描述来说意义不大.在Message属性中键入该列表. | | Ignore | 忽略 | 指定此片段未描述的消息列表.这些消息可发生在运行的系统中,但对此描述来说意义不大.在Message属性中键入该列表. | | Assert | 断言 | 操作数片段指定唯一有效的序列.通常在Consider或Ignore片段中. | | Neg | 否定 | 此片段中显示的序列不得发生.通常在Consider或Ignore片段中. | # 三.七大软件架构设计原则 ## 3.1 开闭原则 ​ 开闭原则(Open-Closed Principle,OCP)指一个软件实体如类,模块和函数应该对扩展开放,对修改关闭.所谓开闭,也正是对扩展和修改两个行为的一个原则.强调的是用抽象构建框架,用实现扩展细节,可以提高软件系统的可复用性及可维护性.开闭原则是面向对象设计中最基础的设计原则.它指导我们如何建立稳定灵活的系统,例如版本更新,我们尽可能不修改源码,但是可以增加新的功能. ​ 在现实生活中,开闭原则也有体现.比如,很多互联网公司都实行弹性制工作时间,规定每天工作8个小时.意思就是说,对于每天工作8小时这个规定是关闭的,但是什么时候来,什么时候走是开放的.早来早走,晚来晚走. ​ 实现开闭原则的核心思想就是面向抽象编程. ## 3.2 依赖倒置原则 ​ 依赖倒置原则(Dependency Inversion Principle,DIP)指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖其抽象.抽象不应该依赖细节,细节应该依赖抽象.通过依赖倒置原则,可以降低类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并降低修改程序带来的风险. ## 3.3 单一职责原则 ​ 单一职责原则(Simple Responsibility Principle,SRP)指不要存在一个以上导致类变更的原因.假设有一个Class负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能会导致另一个职责的功能发生故障.这样一来,这个Class就存在两个导致类变更的原因.如何解决这个问题?我们就要分别用两个Class来实现两个职责,进行解耦.后期需求变更维护互不影响.这样的设计,可以降低类的复杂度,提高类的可读性,提高系统的可维护性,降低变更引起的风险.总体来说就是一个Class,Interface,Method只负责一项职责. ## 3.4 接口隔离原则 ​ 接口隔离原则(Interface Segregation Principle, ISP)指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口.这个原则知道我们在设计接口时,应当注意以下几点. 1. 一个类对另一个的依赖应该简历在最小接口上 2. 建立单一接口,不要建立庞大臃肿的接口 3. 尽量细化接口,接口中的方法尽量少(不是越少越好,一定要适度) 接口隔离原则符合"高聚合,低耦合"的设计思想,使得类具有很好的可读性,可扩展性和可维护性.在设计接口的时候,要多花时间思考,要考虑业务模型,包括还要对以后可能发生变更的地方做一些预判.所以,在实际开发中,我们对抽象,业务模型的理解是非常重要的. ## 3.5 迪米特法则 ​ 迪米特法则(Law of Demeter, LoD)又叫作最少知道原则(Least Knowledge Principle, LKP),指一个对象应该对其他对象保持最少的了解,尽量降低类与类之间的耦合.迪米特法则主要强调和朋友交流,不和陌生人说话.出现在成员变量,方法的输入和输出参数中的类都可以被称为成员朋友,而出现在方法体内部的类不属于朋友类. ## 3.6 里氏替换 ​ 里氏替换原则(Liskov Substitution Principle, LSP)指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型. ​ 定义看上去比较抽象,重新解释一下,可以理解为一个软件实体如果适用于一个父类,则一定使用于其子类,所有引用父类的地方必须能透明地使用其子类对象,子类对象能够替换父类对象,而程序逻辑不变.也可以理解为,子类可以扩展父类的功能,但不能改变父类原有的功能.根据这个理解,我们对里氏替换原则的定义总结如下: 1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法 2. 子类中可以增加自己的特有方法 3. 当子类的方法重载父类的方法时,方法的前置条件(即犯法的输入参数)要比父类的方法更加宽松 4. 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类的方法更严格或相等. ## 3.7 合成复用原则 ​ 合成复用原则(Composite/Aggregate Reuse Principle, CARP)指尽量使用对象组合(has-a)或对象集合(contains-a)的方式实现代码复用,而不是用继承关系达到代码复用的目的.合成复用原则可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他的类造成的影响相对较小. ​ 继承,又称为白箱复用,相当于把所有实现细节暴露给子类.组合/聚合又被称为黑箱复用,对类似以外的对象是无法获取实现细节.我们要根据具体的业务场景来做代码设计,其实也都需要遵守面向对象编程(Object Oriented Programming, OOP)模型. ## 3.8 软件架构设计原则总结 | 设计原则 | 一句话概括 | 目的 | | ------------ | ------------------------------------------------- | ---------------------------------------- | | 开闭原则 | 对扩展开放,对修改关闭 | 降低维护带来的风险 | | 依赖倒置原则 | 高层不应该依赖低层 | 更利于代码结构的升级扩展 | | 单一职责原则 | 一个类只干一件事 | 便于理解,提高代码可读性 | | 接口隔离原则 | 一个接口只干一件事 | 功能解耦,高聚合,低耦合 | | 迪米特法则 | 不知道的不要知道 | 只和朋友交流,不和陌生人说话,减少代码臃肿 | | 里氏替换原则 | 子类重写方法功能发生改变,不应该影响父类方法的含义 | 防止继承泛滥 | | 合成复用原则 | 尽量使用组合实现代码复用,而不适用继承 | 降低代码耦合 |