UML建模语言、设计原则、设计模式

2022-12-20,,,,

1、UML统一建模语言

定义:用于软件系统设计与分析的语言工具

目的:帮助开发人员更好的梳理逻辑、思路

学习地址:UML概述_w3cschool

官网:https://www.omg.org/spec/UML

1.1、UML组成结构

flowchart TD;
UML图 --> 结构图 & 行为图;
结构图 --> 剖面图 & 包图 & 复合结构 & 对象图 & 类图 & 组件图 & 部署图
行为图 --> 交互图 & 活动图 & 状态图 & 用例图
交互图 --> 交互概图 & 时序图 & 顺序图 & 通信图

1.2、各类UML图示例

画图工具可以用亿图图示或其他工具,亿图图示可以自行在微信订阅号中搜索:亿图图示V10破解即可

懒得找的话也可以直接去这里:https://mp.weixin.qq.com/s/bfg_D8ADLZ7KV7PjGBEH5A

1.2.1、用例图

定义:用来显示一组用例、参与者以及它们之间关系的图

是从需求分析出发,不考虑代码实现的事,描述用户希望如何使用一个系统。通过用例图可以知道谁是系统相关的用户,他们希望系统提供哪些服务,以及他们需要为系统提供什么样的服务

1.2.1.1、用例图组成

名称 含义 图例
参与者
(Actor)
也叫角色,表示系统的用户(在系统之外,但与系统直接交互的对象)
注:这里的用户并不一定是指人,如:做的是公共API接口,那API的调用者就是用户
用例
(Use Case)
描述参与者可以感受到的系统服务或者功能(换言之:描述系统为了实现用户的目标而执行的一个系统功能单元)

注:用例的目标是要定义系统的一个行为,但并不显示系统的内部结构 / 某个功能的具体实现

系统边界 也叫容器(但这个名字词不达意),系统与系统之间的界限 两种都对,但最常用的是矩形
子系统
(SubSystem)
一堆用例的集合,这堆用例之间有着紧密关系(换言之:展示系统的一部分功能)

1.2.1.2、用例图之间的关系

符号 名称 说明 图示
—————— 关联 参与者与用例之间的通信(参与者 和 用例之间的关系)
--------> 包含 提取公共交互,提高复用
换言之:一个用例需要某种功能,而该功能被另外一个用例定义,那么在用例的执行过程中,就可以调用已经定义好的用例(用例 与 用例之间的关系)

箭头指向:指向分解出来的功能用例

扩展 基用例保持不动,动态扩展基用例的功能(用例 与 用例之间的关系)
扩展关系的限制规则(也是区别包含关系的手段):将一些常规的动作放在一个基本用例中,将可选的或只在特定条件下才执行的动作放在它的扩展用例中

箭头指向:指向基用例

表示方式使用表中左边说的那种符号或者下图这种和包含一样,构造型换一下也行(通常用的是下面这种)
泛化 / 继承 子用例中的特殊行为都可以作为父用例中的备选流存在(用例 与 用例之间的关系[父子用例] )

箭头指向:指向父用例(箭头实心和空心都可以,严格来讲是空心)

<<include>> 构造型 就是扩展的意思(UML中通用的扩展表现形式),相当于说明

include 是包含关系关键字
extend 是扩展关系关键字

用例图实例展示

注:下图 出钞 和 凭条与退卡 是说的物理机ATM保险柜的功能,并不是说出钞 和 凭条与退卡 是客户从提款机中看到的这二者功能( PS:ATM分为两部分,一部分是我们所做的软件系统,即:下图的ATM系统,另一部分是ATM保险柜[钱真正在的地方],可以说就是硬件,自动取款只是通过我们编写的软件系统去操作了保险柜,从而把钱吐出来)

1.2.2、活动图

活动图本质是流程图,从流程图演变而来的。

定义:对系统的动态行为建模的一种工具,描述的是活动的顺序,即:从一种活动到另一种活动的控制流(本质:活动之间控制权的转换)

对于上述我所谓的活动之间控制权的转换的说明,如:我去进行核酸检测(下图不严谨,当流程图来看,只是混入了活动图的图标在里面)

通过下图可以推论出:控制权不会丢失,可以分散 / 分支、最后也会合并,不会消失,只是从一个活动到了另一个活动“手里”而已(像能量守恒一样)

1.2.2.1、活动图的组成

1.2.2.1.1、基本组成
名称 定义 符号
开始状态 表示活动开始的一个状态

注:开始状态只能有一个

下面两种表示方式都可以
结束状态 表示活动结束的一个状态

注:结束状态可以有多个

下面两种都行
活动 / 动作 很多动作的集合
一个动作就是一个步骤

如:打篮球就是一个活动,但是:里面却可以有很多动作,譬如:分组、进攻、防守......当然这些还可以再细分

另外:动作其实就是子图(即:一个活动的内部逻辑。后续会说明)

状态 和活动等价
特别点:严格来讲状态只分为开始状态和结束状态,活动符号并没有上面那种表示法(新版。旧版有),现在官网中对活动符号的表示如下:
注意:和活动符号的图不太一样,当然:用哪一个都可以
控制流 就是控制权的流动方向,也有人叫“转移” 下面两种表示方式都可以
对象 某个类的实例或者是某些活动输出的结果(可以理解为是一个参数,某个活动状态需要借助某个参数,借助的这个参数就是一个对象)
在整个活动图中一个对象是可以多次出现的(类的实例嘛)
注意对象名称下面是有下划线的

另外:对象名称注意用名词来进行命名

对象流 可以理解为数据流
就是活动与对象之间的数据传递,也就是活动之间需要某个 / 某些对象来参与,那么:控制流就变成了对象流
下面二者都可以
流终止 表示控制流 / 对象流的结束
这个其实可以不要,终止了不在图中表示出来不就表示终止了吗
事件 可以理解为信号
分为发出信号 和 接收信号
下图的中间两个,左为发出信号,右为接收信号

逻辑:
处理订单后,发出请求付款的信号
活动等待接收确认付款的信号
活动接收到了付款信号之后,即发货

还有一种事件:叫时间事件(也可以当做是一个活动)
就是等待某一个时间才能触发某个活动
时间名称放在符号下方

判定活动 就是流程图中的逻辑判断 注意:这个不是分支或者合并,还差一点东西才能变成分支 或 合并(就是几根控制流的线),有了这一步才能说创建分支
同步条 就是控制流(控制权)的控制
下面看到了分叉与会合之后就一清二楚了
分为水平同步 和 垂直同步(二者没区别,是画图的方向问题,看画的图箭头方向是怎样的,然后选择对应的同步条即可)

分支与合并(都需要判定活动参与)

分支:可以理解为控制权的分散(一个活动的控制权分给了多个活动),要求:必须是一个控制输入流、两个及以上的控制输出流,符号表示方式如下(菱形+四个控制流箭头):

注意:判定活动(即:菱形)不是分支,判定活动+控制流才是分支
合并:可以理解为控制权的融合(多个活动的控制权给到了一个活动),就是分支的逆向。要求:多个控制输入流、一个控制输出流,符号表示如下:

分叉与会合(都需要同步条参与)

分叉:用于将一个控制流分为两个或多个并发运行的分支,要求:必须是一个控制输入流、两个及以上的控制输出流,符号表示如下:

会合:用于将两个或多个控制流合并到一起形成一个单向的控制流,要求:多个控制输入流、一个控制输出流,符号表示如下:

泳道

定义:表明每个活动是由哪些对象负责完成的(换言之:表示活动的发起者是谁,对象不一定非要是人,可以是系统、会员........),也可以说是:一个对象进行了哪些活动。当然:可以换个名字就更好理解了,即:分区(一个区域中有哪些活动状态)
泳道分类:水平泳道和垂直泳道,和前面的同步条是一样的,水平和垂直没什么区别,也是画图方向的问题,符号表示如下:

实例:

子图 / 子活动图

在前面的表格中提到过,就是动作(活动是动作的集合体,类似Java中的对象 ---抽象---->类,很多动作 ------抽象------>活动),可以理解为:是对某个活动画的补充图,只不过这个补充图是较为详细的逻辑表现(类似一个活动需要引入的粗糙点的流程图)
定义:对某个活动进行的续图说明,符号表示就是一个倒着的“扫把”(下图这种颜色的图是我在官网下载的文档中嫖的,版本是2.5.1)

左边活动中有一个倒着的扫把就表示这个活动要引入一个子图,而右边就是引入的子图内容
注意:动作和活动这两个不能说完全等价(钻字眼儿),用上图举例:
如果左边的活动里面的一部分流程描述 / 活动组成内容刚好在另一个真正的活动图中分毫不差地体现了,而左边这个活动需要引入,那么此时就可以说子图就是活动,即:动作等价于活动
如果左边的活动里面需要的部分流程描述 / 活动组成内容没有找到其他活动图来完全贴合其描述,那么就是需要新画一个子图来对左边的活动进行简略描述,继而在左边活动中引入,则:此时子图是动作,而不是真正的活动,即:动作不等价于活动

扩展区域 / 扩充区 / 扩展区

定义:将一个需要体现在活动图中的循环过程进行提取(不需要体现在活动图中的,可以直接使用活动节点来略写),有点类似于子图,但是扩展区的关键就是提取的是一个活动中的循环过程,但不是把循环过程重新弄成一个活动图,而是就在当前活动图中
符号表示如下(左为简单写法,右为完整写法):

实例:

1.2.3、类图、对象图

定义(人话):就是表示一个类 / 接口的组成结构

​ 对于属性:看修饰符是什么(public、private、static等)、数据类型是什么、属性名叫什么、是否有默认值

​ 对于方法:看修饰符是什么(public、private、static等)、返回值是什么、方法名是什么、参数类型和名字是什么

​ ..................

关键字 表示方式
public + 表示
private - 表示
protected # 表示
package ~ 表示
abstract *表示
static 用 $ 表示
泛型 ~泛型类型~表示 如:List~int~ position
注解 以<<开头 注解内容 以>>结尾

可以用一个特定的标记文本来注释,如:
<<Interface>> 代表一个接口
<<abstract>> 代表一个抽象类
<<Service>> 代表一个服务类
<<enumeration>> 代表一个枚举

注释 %%注释内容 表示
注释开始到下一个换行符之前的任何文本都将被视为注释,包括任何类图语法
这是对类图进行注释,即:说明,不是说属性、方法.....都搞这个

对于类:以 https://www.processon.com 网址中的为例(下列名字见名知意,对照上面的人话定义即可)

对于接口:和上面的类图是相通的

1.2.3.1、类图之间的关系

名字 指向 示例 图示
泛化 / 继承 子类 指向 父类
子抽象类 指向 父抽象类
学生类 继承 人类 实心三角箭头 和 空心三角箭头都行
组合 菱形部分指向整体
是属于包含关系中的一种(组合、聚合、关联)
A has - a B的关系。一句话:一荣俱荣、一毁俱毁
整体和部分关系、整体部分不可分离、比聚合更强
如:一个类中的一个属性为private Head head = new Head(); ,直接绑定在一起的
大雁和大雁翅膀的关系,两者是同生共死的
聚合 菱形部分指向整体[箭头指向个体,这个箭头可有可无]
是属于包含关系中的一种(组合、聚合、关联)
A has - a B的关系。
还是整体和部分的关系,但是创建时有可能是分隔开的
如:一个类中的属性为private IDCard card;这个属性值可能会后续在其他地方传进来(有参构造)
大雁和雁群之间的关系
更如:电脑和主板
关联 / 关联类 箭头指向成员变量类(单关联)。这种单关联注意一种模型图(最严格的一种写法):在没有箭头的一方可能会有一个“×”,这表示:箭头的一方一定没有关联“×”的一方。反之:没有“×”表示当前模型中没有明确说明无箭头一方是否关联有箭头一方

箭头指向彼此(双关联),如:A类中有B类作为成员变量(属性),B类中有A类作为属性,此时彼此都产生关联,即为双向箭头 / 双关联

还有一种写法:两边都没有箭头,就是一根实线,这种直接说是包含关系(是一种不严谨的写法),这种直接当做属性,A类和B类至于是单关联还是双关联都行

注意一种情况:下面这种 下图不等价于上图(它们表示“不同组”,即:上图表示Car关联的是驾驶这辆Car的Person,而Person驾驶的是同一辆Car;而下图表示Car关联的是驾驶这辆Car的Person,但Person关联 / 驾驶的是另外的Car[某辆车需要人类中的某个人来驾驶,而人类中的另外某个人可以驾驶另外型号的车])

关联关系是属于包含关系中的一种(组合、聚合、关联)
A has - a B的关系
是整体和部分的关系,可以分割,是后来组合在一起的
换言之:两个类之间的关联,也可以是一个类和自身的关联

班级类和学生类,学生类作为成员变量存在于班级类中
也如:人有汽车、人有电脑
类关联 这就是比关联类更细节性的画图(让某些情景不产生歧义)
抽象了多对多的本身(A集合和B集合各自全集的笛卡尔乘积的子集)
下图为关联类

上图表达的逻辑:1、一个人有多条出席会议记录,对应的是一个会议[图符合,因为这个人可能会和不同的人开同一个会议];2、一个会议有2或以上条出席会议记录,对应的是一个人[图也符合,因为这个会议某个人可能参加了多次]。所以虽然图看起来没问题,但是不贴合现实[一个人出席了同一个会议那记录为1就可以了,按上图来看就会出现某个人有多条出席会议记录 - 即:重复了],所以上图中“总的出席会议记录数量大小”在数学上应该是:多人、多会议各自全集的笛卡尔乘积的子集[会排除重复的],因此为了贴合现实图就改造为如下的类关联

逻辑:1、2或以上的人对应多个会议(多对多);2、出席会议记录类关联了人和会议两个类,并且出席会议记录最后的结果为两边集合各自的全集的笛卡尔乘积的子集[排除重复的结果])

依赖 箭头指向入参类
A need - a B 的关系
班级类和学生类,班级类作为学生类的方法入参
实现 箭头指向接口 学生类实现人类
1.2.3.1.1、各类图关系实例
1.2.3.1.1.1、泛化 / 继承
public class DaoSupport{

    private String name;

    public void save(Object entity){}

    public void delete(Object id){}
} public class PersonServiceBean extends Daosupport{
}

使用typora的mermaid脚本画图

语法:

<|-- 表示继承 箭头指向的一方是被继承者

+ 表示 public

- 表示 private

语法学习地址:Markdown教程-慕课网 (imooc.com)

mermaid语法学习地址:https://mermaid-js.github.io/mermaid/#/


```mermaid classDiagram
%% 要用注释只能放在这里,对类图进行说明
class DaoSupport{
属性返回值类型放在前面 -String的-和String中间最好也别搞空格(只是这个mermaid脚本中而已),但是严格写法应该是 -name : String
-String name
方法返回值类型是在后面 另外:+void的+和void之间别搞空格
+save(Object entity) void
+delete(Object id) void
} class PersonServiceBean{ } DaoSupport <|-- PersonServiceBean
```

效果如下:

classDiagram
%% 这是一个小试牛刀的类图
class DaoSupport{
-String name
+save(Object entity) String
+delete(Object id) void
}

class PersonServiceBean{

}

DaoSupport <|-- PersonServiceBean

1.2.3.1.1.2、组合

public class Person { // 组合关系:某个类的对象 当做 当前类的属性,并已经new了
private Head head = new Head();
}

mermaid脚本画图语法:

*-- 表示组合,星* 指向的是整体 即:菱形指向整体

```mermaid

    classDiagram

        class Person{

        }

        Person *-- Head  // 表示的是:Person 组合 Head
```

效果如下:

classDiagram

class Person{

}

Person *-- Head

1.2.3.1.1.3、聚合
public class Person {
// 聚合关系
private IDCard card; // 对照:组合关系
private Head head = new Head();
}
```mermaid

classDiagram
class Person{ } Person o-- IDCard // 这是字母o 不是0,菱形指向整体 即:Person聚合了IDCard ```

效果如下:

classDiagram
class Person{

}

Person o-- IDCard

1.2.3.1.1.4、关联、依赖、实现

1.2.3.2、对象图

定义:表示在某时刻对象和对象之间的关系(由于对象存在生命周期,因此对象图只能在系统某一时间段存在)

对象图是类图的实例,几乎使用与类图完全相同的标识。一个对象图可看成一个类图的特殊用例

1.2.4、顺序图(时序图 / 序列图)和通信图

1.2.4.1、顺序图

定义:用来表达对象间消息传递的顺序

一般来说:顺序图也叫时序图、序列图(这三个在英文中都是Sequence ),但是:严格来说(电子通讯方面),顺序图是顺序图,时序图 / 序列图是时序图 / 序列图(在电子通讯方面,这个实在要对应的话,就对应UML中的时间图Timing Diagram),在电子通讯领域这二者要表达的意思并不一样,但是对于我们编程这一行业来说:直接把顺序图、时序图、序列图等价也没错,叫其中哪一个名字都无所谓

1.2.4.2、顺序图组成

markdown中画时序图语法(本质是使用了mermaid脚本):https://www.imooc.com/wiki/markdownlesson/markdownsequencediagram.html
mermaid脚本语法:https://mermaid-js.github.io/mermaid/#/sequenceDiagram

名称 说明 图示
参与者 也叫角色,表示系统的用户(在系统之外,但与系统直接交互的对象)
注:这里的用户并不一定是指人,如:做的是公共API接口,那API的调用者就是用户
对象 就是对象图中的对象,可理解成某个类的实例 如果只显示类名,则:去掉上图中“对象名”即可,即: :某个类类型
如果只显示对象名而不显示类名,则:去掉 : 及之后的即可,即: 对象名
生命线 表示对象的生存时间(就是一条向下的虚线)
激活 表示某种行为的开始或结束,就是一个小矩形
反之:没有小矩形的那些虚线就是对象的休眠
消息 分为同步消息 和 异步消息
在UML中,指的是:对象与对象之间的通信
在顺序图中是用 两个对象之间带箭头的线来表示
注:下图真实含义是另一个,拆开看,单独只看两个带箭头的线即可,整个图的场景是另一个意思

带实心箭头的实线:发送消息 / 方法调用
带开放式的三角箭头的虚线:返回消息 / 返回值(特别需要返回消息时就用,不特别需要的话,那么采用下面同步那种简化画法就可以了)

同步消息 就是消息发送完毕了,就返回消息(一条龙服务)
当然:这也意味着阻塞和等待
当然:上图也可以换成前面那种用“开放式的三角箭头的虚线返回消息”,上图这种是简写形式
注意:1、图中参数哪里要是没有,不表示就没有消息的发送(调用方法,方法本身就是消息发送);2、如果没有返回值,即:void那也不表示没有返回消息(当进行方法调用时,右边已经激活,一样会进行阻塞,即:右边激活条做完该做的事情,照样给左边返回信息,告知调用者事情做完了,只是图中不会显示地画出来而已,但内部逻辑还是有的)
异步消息 和同步换一下,就是消息发送完毕了,返回消息可以后面给(中间可以做完另外的事情再给)
同理:这就意味着非阻塞

注意:和同步消息画法不一样(箭头不一样),另外:异步中返回消息不是虚线,是实线(就是变成右边对象向左边对象发送消息:内容就是左边对象要的返回消息 / 返回值)

持续消息时间 字面意思
出现的情况:有些消息需要持续很长一段时间,从而需要标注出来(如:大文件上传)
第一种:使用带有实心箭头的倾斜的线表示(下图 {} 中括号中是条件控制,在后续会介绍)

第二种:表达准确的时间(在第一种的基础上,继续加入东西)

上图表示:在2h内上传文件,然后返回结果之后等待5min以上,检查上传情况

重入消息 A对象给B对象发消息,在B还未返回消息之前,B给A发了一条消息
自我调用 是重入消息的特例(A给B发消息,在B未返回之前,A又给自己发了一条消息)
所以就是自己玩自己(俗称:自wei)
下面两种画法都可以(严格来讲是第一种)
无触发和无接收消息 上面那些都是基于系统本身内部的,但是:有些可能需要使用到系统外部的某些东西(对象、参与者....)
在技术实施开发层面一般不会见到,其他岗位会有
对象的创建 字面意思 被创建对象会比生命线矮一截(就是下图中右边比左边矮一点)
对象的销毁 字面意思,表示方式就是在对象销毁时打一个“×”

1.2.4.3、执行控制

关键字 说明
alt 备用多个片段:只执行条件为真的片段(就是条件分支if else
opt 可选项:仅当提供的条件为真时才执行片段。相当于只有一条痕迹线的alt
par 并行:每个片段并行运行
loop 循环:片段可以执行多次,并且防护指示迭代的基础
region 关键区域:片段只能有一个线程一次执行它
neg 否定:片段显示无效的交互
ref 参考:指在另一个图中定义的交互,绘制框架以覆盖交互中涉及的生命线(可以定义参数和返回值)
sd 序列图:用于包围整个序列图

上面的都是官方话,接下来举一些常用的例子。

1.2.4.3.1、条件分支 alt

1.2.4.3.2、可选项 opt

包含一个可能发生或不发生的序列

只要当我成绩score小于60时,老妈打我这件事情肯定会发生。大于就不会发生

1.2.4.3.3、循环 loop

图是网上偷的,看懂了分支,那循环也能很容易看懂了

1.2.4.3.4、并行 par

1.2.4.4、顺序图实例

SpringMVC执行流程原理

转化为顺序图

1.2.4.5、通信图

通信图和顺序图可以等价互转

通信图牺牲了顺序上的直观性,增强了布局和关联上的直观性;而顺序图是相反的

要搞逻辑就看序号(1:1.1:2:2.1:..........)

假如一个序列图如下:

转化为通信图(两张图对照看就懂了):

1.2.5、状态图 / 状态机图 / 转移图

定义:状态图又名状态机图或转移图,指的是:一个特定对象的所有可能的状态以及引起状态转换的事件

一个对象必然会经历一个从开始创建到最终消亡的完整过程,这称之为对象的生命周期。对象在其生命周期内是不可能完全孤立的,它必然会接受消息来改变自身,或者发送消息来影响其他对象。而状态机就是用于说明对象在其生命周期中响应时间所经历的状态序列以及其对这些事件的响应。在状态机的语境中,一个事件就是一次激发的产生,每个激发都可以触发一个状态转换

1.2.5.1、状态图的组成

一份简单的状态图

状态图组成

名称 说明
状态 指的是对象在其生命周期中的一种状况,处于某个特定状态中的对象必然会满足某些条件、执行某些动作或者是等待某些事件
动作 指的是状态机中可以执行的哪些原子操作
原子操作:指的是他们在运行的过程中不能被其他消息中断,必须一直执行下去,以至最终导致状态的变更或者返回一个值
事件 指的是发生在时间和空间上对状态机来讲有意义的那些事情
事件通常会引起状态的变迁,促使状态机从一种状态切换到另一种状态
活动 指的是状态机中做的那些非原子操作
转移 指的是两个不同状态之间的一种关系,表明对象在第一个状态中执行一定的动作,并且在满足某个特定条件下由某个事件触发进入第二个状态

1.2.5.2、状态图的五要素

注意:

上图中“触发事件”和“监护条件”要同时生效的,即:只有“触发事件”满足“监护条件”,才能执行“动作”,从而让第一个状态转移为第二个状态

“监护条件”写法就是[监护条件][]不能丢
上图中整个“带箭头的三角的实线(单纯地这根线)”就是“转移”,或者称之为:行为状态也行
上图中整个“带箭头的三角的实线+上面的触发事件[监护条件]/动作”就是事件,其中:触发事件[监护条件]/动作 三者少哪一个都行,甚至全没有也行

1.2.5.3、状态图中的状态

注:初态和终态都不是真正的状态,而是:伪状态

名字 说明 图示
初态 一个状态图有且只有一个初态 黑色实心
终态 一个状态图中可以有一个或多个终态,也可以没有终态 一对同心圆(内圆为实心圆)
中间态 用圆角矩形表示,可以用一条水平横线把它分成上、下两部分。(面部分为状态的名称[必有]面部分是活动表[可选]

有些软件是分成了三部分:面部分为状态的名称(必有)间部分为状态变量的名字和值(可选)面部分是活动表(可选)

子状态 就是状态中套状态
历史状态 就是一个对象曾经已经发生过的状态的记录,类似历史日志
作用:用来恢复状态的。如:断电了,导致系统整个状态结束了,恢复电之后想要回到断电时的状态就可以用
可以多层嵌套,就是一个套一个(有可能要恢复的状态在很多层里面)

举例:

对于上面表格的中间态中说的活动表的说明:

活动表又名转换域

表达式语法为:事件名(参数表)/动作表达式,其中:参数表也可以没有,如下:

在活动表中经常使用的3种标准事件

do 上图已经见过了,指的是:在该状态下的动作
entry 进入该状态的动作
exit 退出该状态的动作

1.2.6、组件图 / 构件图

定义:用来描述一个系统 / 功能的物理构件( 组件与组件之间的关系 )。包括文件,可执行文件,库等。换言之:构成系统某一特定方面的实现结构

组件图 = 构件 / 组件(Component)+接口(Interface)+关系(Relationship)+端口(Port)+连接器(Connector)

1.2.6.1、组件图的组成

1.2.6.1.1、组件

定义:是一个封装好的物理实现单元,隐藏内部的实现,对外提供了一组接口

具有自己的身份标示和定义明确的接口。由于它对接口的实现过程与外部元素独立,所以组件具有可替换性

人话:组件就是一个实际的文件或者多个文件组成的可执行程序(通俗的话来说[严格来讲不能这么理解,但是为了理解而理解,可以用]:组件就相当于Java的抽象和封装思想(当然:懂Vue的话,那就懂组件化开发了,那就更不用解释了)

组件的种类:

源代码组件:一个源代码文件或者与一个包对应的若干个源代码文件
二进制组件:一个目标码文件,一个静态的或者动态的库文件
可执行组件:在一台处理器上可运行的一个可执行的程序单位,即所谓的可执行程序

组件长什么样子(UML1.x的画法)

1.2.6.1.2、组件盒(就是组件)

定义:就是一个用来装组件的盒子

当然:组件盒其实就是组件,这二者就是等价的,因为这盒子里面装的就是组件,因此:UML2.x中,组件就是组件盒

组件盒长什么样子

因此:组件的画法就可以弄成下面几种了

矩形+图标

矩形+构造型标签,就是上面组件盒的画法,下图构造型标签<<>>中写组件中文名字也行(但:建议用英文关键字)

前面两者都有的画法,这种画法构造型标签<<component>>就只起到标识作用

1.2.6.1.3、接口

分为两类:提供接口 和 需求接口

提供接口:又被称为导出接口或供给接口,由提供操作的组件提供,是组件为其他组件提供服务的操作的集合(如:商品组件提供商品相关的一堆接口)

需求接口:又被称为引入接口,是组件向其他组件请求相应服务时的接口(订单组件需要调用商品组件提供的接口)

提供接口长什么样子?

需求接口长什么样子?

1.2.6.1.4、端口

这个已经在熟悉不过了
就是一个被封装的组件的对外窗口
在封装的组件中,所有出入组件的交互都要通过端口。它是被封装的组件与外界的交互点,遵循指定接口的组件通过它来收发消息
表示方式:就是一个小矩形

1.2.6.1.5、连接器

指的就是组件间的连接,换言之:就是组件之间的关系,也就是在类图中的实线、泛化关系........等等,所以:连接器不只是在组件图中,在UML图中都有,就是那些线嘛(在组件图中,这种关系有个专业名词叫:组装连接器,还有一个委托连接器:连接外部接口的端口和内部接口[这个不需要多了解])

实现关系:用直线表示
依赖关系:用带箭头的虚线表示
额外补充:组件依赖的表示方式

上图中上面那种也叫插座表示法,和下面的表示方式是等价的

1.2.6.1.6、前几者组合在一起的样子

外面大的那个就是容器,和包图很像(但不太一样)

既然提到了包图,那就一次性弄完:

包图:见名知意。和平时接触的包依赖关系一样。如:A包导入B包,那A包可以使用B包的东西

单个包图完整样子是下面这个鬼样

搞到了包图,那就解释一下前面连接器那里:为什么连接器是通用的问题

为什么连接器可以通用?有个包关系是如下的样子

即:混合结构(Composite Structures)中导入了类图(classes),后面的依次看,继而推出:混合结构(Composite Structures) 是类图(classes)的一种扩展,同理:组件图中就有了混合结构和类

1.2.6.1.7、混合结构

上面提到了混合结构,那也来搞一下

混合结构的意思就是字面意思,混合嘛,即:类图、组件图......混合使用(开发中的那个画法就是)

解读:
整个Car大框就是类图,类图中的属性(Car下面的那个属性的大框)变成了组件图(组件图中再套组件图.......),组件图中的属性表示方式和类图中一样(-为private、+为public,属性名、属性类型.....)

1.2.6.1.8、组件图示例

在网上嫖的图,意思意思

1.2.7、部署图

定义:描述的就是物理层面的模型,就是让系统和硬件打上交道

部署图与组件图相同的构成元素:

组件、接口、组件实例,提供接口(组件向外提供服务)、需求接口(组件要求外部提供的服务)

部署图与构件图的关系:

部署图表现组件实例; 组件图表现组件类型的定义

部署图偏向于描述组件在节点中运行时的状态,描述了组件运行的环境

组件图偏向于描述组件之间相互依赖支持的基本关系

上面提到了组件和组件实例,其实只是不同的称呼而已,在组件图中都已经见过,只是换成部署图中名字有细微区别而已,符号都是一样的

1.2.7.1、部署图的组成

1.2.7.1.1、物件 / 构建 / 组件

定义:就是被部署的东西

长什么样子在组件图中已经见过了(参考:1.2.6.1.1、组件)

1.2.7.1.2、节点

定义:运行时对象和组件实例驻留的位置(把方向放大一点:也可以说是物件要部署的目标位置,即:物件要部署到哪里去)

节点画法:

针对于各对象 / 各节点的部署来说:描述的各节点之间的关系(也叫实例层部署)

1、前面玩过的那些类图、对象图、组件图中的关系都可以用,部署图就看部署的是什么

各对象关系部署(就是对象图简化版,换个意思,换个场景[部署],细微换一下画法罢了),支持:一对多、多对多

官方文档中是这样介绍这种部署的

2、各节点关系部署,支持:一对多、多对多

再加一点东西就变成官方文档中说的:节点实例部署,支持:一对多、多对多

针对节点以及其包含的组件的部署(这种也叫描述层部署),官方文档称之为:组件 / 工件部署(工件:指的是任何留下记录的事物都可以称之为工件),叫法无所谓,支持:一对多、多对多

1.2.7.1.3、物件与结点的关系:部署

就是下图中组件和整个节点的关系(deploy)

如官方文档中

1.2.7.1.4、结点与结点的关系:通信路径

指的就是下图中的那根线+通信方式

2、设计原则

单一职责(Single Responsibility Principle):一个类和方法只做一件事(有且仅有一个原因引起它的[类和接口]改变)

开闭原则(Open Closed Principle):抽象架构,扩展实现

里氏替换(Liskov Substitution Principle):多态,子类可以扩展父类

迪米特原则(Law of Demeter):最少知道,降低藕合

接口隔离(Interface Segregation Principle):建立单一接口 / 接口不可再分

依赖倒置(Dependence Inversion Principle):细节依赖抽象,下层依赖上层(也可以叫做面向接口编程)

以上几个的第一个单词首字母合起来就是(里氏替换原则和迪米特法则的首字母重复,只取一个): SOLID(坚硬的)

2.1、单一职责原则

定义:一个类和方法只做一件事(有且仅有一个原因引起它的[类和接口]改变)

如打电话分为:拨通、交流、挂断,类图如下

但是这样是有问题的,因为:dial拨通、hangup挂断是属于链接管理,而chat交流是属于数据传送,因为这个IPhone接口就有两个原因导致它发生变化了,因此:进行改造,抽离(面向接口编程,对外公布接口,不公布实现类)

2.2、开闭原则

定义:抽象架构,扩展实现

具体意思:指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。也就是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化

如书店卖书,类图如下:

接下来搞打折活动,这时不可能说是去修改IBook接口,在里面新增一个打折getOffPrice()的方法吧,这样的话,那实现类NovelBook需要改源码,而关联类BookStroe也需要修改。甚至也不可能直接在实现类NovelBook中把getPrice()的逻辑进行修改,这两个方法都不行,因此:应该改成如下的样子

重新来一个OffNovelBook类继承NovelBook,从而重写getPrice()方法,这样就做到:对扩展开放[原价格可以在NovelBook类的getPrice()中拿到,打折后的价格可以在扩展类OffNovelBookgetPrice()方法中拿到]、对修改关闭[并不需要动原有的代码]

2.3、里氏替换原则

定义:多态,子类可以扩展父类(所以得知:针对的是继承)

里氏替换原则通俗易懂的定义是:只要父类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常(在开发中的场景是:定义一个接口或抽象类,然后在使用其实现类时,传入接口类型的参数,即:多态),但是:又包含4层含义

1、子类必须完全实现父类的方法
2、子类可以有自己的属性和方法
3. 覆盖或实现父类的方法时,输入参数可以被放大(如:父类中方法的参数类型是hashMap,子类重写时参数类型可以Map类型)
4. 覆盖或实现父类的方法时,输出结果可以被缩小(对照3)

2.4、迪米特原则

定义:也叫最少知道原则,即:一个对象对其他对象应该有最少的了解,有另外一个英文解释:Only talk to your immediate friends(只和直接的朋友交流)

核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提升上去

例子的话:生活中最常见,即:脚踏两只船,针对于两个女票之间,这两个对象最好还是少知道点彼此的事情比较好吧,不然试试?

2.5、接口隔离原则

定义:建立单一接口 / 接口不可再分,或者说是:类间的依赖关系应该建立在最小的接口上

如搜索美女,类图设计假如是下面的样子

但是如果按上图来弄的话,那IBeauty美女接口中定义的是:好颜值 or 好身材 or 好气质的才是美女,可是:每个人的品味都不一样啊,可能有人认为这三者中的任意一种组合就叫美女,因此:需要把上面的类图改造成如下的样子:

重构以后,不管以后需要颜值美女,还是需要身材美女,亦或气质美女,都可以保持接口的稳定性
以上把一个臃肿的接口拆分为三个独立的接口所依赖的原则就是接口隔离原则,即:最小接口 / 建立单一接口嘛

2.6、依赖倒置原则

定义:细节依赖抽象,下层依赖上层(也可以叫做面向接口编程)

1、模块间的依赖通过抽象发生,实现类之间不直接发生依赖关系,其依赖关系是通过接口或抽象类产生的;

2、接口或抽象类不依赖于实现类;

3、实现类依赖接口或抽象类

依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性

依赖倒置原则的使用建议:

1、每个类尽量都有接口或抽象类,或者接口和抽象类两者都具备。

2、变量的表面类型尽量是接口或抽象类。

3、任何类都不应该从具体类派生。

4、尽量不要重写基类的方法。如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要重写。

5、结合里氏替换原则使用

3、设计模式

分类:

注:使用设计模式的规范,类名 = 需求名+使用的对应设计模式名,如:StringBuilder,这就是使用了Builder建造者模式

设计模式不是一成不变的,主要是思想,至于形不需要在意,形只是便于理解罢了

3.1、创建型

这个类型的模式是专门针对于创建对象的,也就是它的适用机制

3.1.1、单例模式

定义:保证对象全局唯一,即:保证一个类只有一个实例,哪怕是多线程来进行访问,向外提供一个访问此实例的方法即可

使用场景

1、数据库连接池不会反复创建
2、Spring中一个单例模式Bean的创建
3、开发中设置一些全局的属性进行保存(当然:用Redis更好)

3.1.1.1、static实现

package com.zixieqing.o1static;

import java.util.HashMap;
import java.util.Map; /**
* <p>@description : 该类功能 使用static的方式</p>
* <p>@package : com.zixieqing.o1static</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class StaticSingleton { private static Map<String, String> CACHE = new HashMap<String, String>();
}

这种方式在第一次运行时就初始化Map了,不需要延迟加载
缺点:需要被继承 或 需要维持一些特定状态时就不适合了

3.1.1.2、懒汉模式

定义:体现在一个“懒”字上,即:需要时才去创建对象

package com.zixieqing.o2lazy;

/**
* <p>@description : 该类功能 懒汉式
* 此种方式不安全:好比多个人抢厕所,会造成不安全,可能有多个人抢到
* </p>
* <p>@package : com.zixieqing.o2lazy</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class LazySingleton { /**
* 1、private static 当前类的属性
*/
private static LazySingleton LAZY_SINGLETON_INSTANCE; /**
* 2、private 的构造
*/
private LazySingleton() {
} /**
* 3、提供public static 创建当前类对象的方法
*/
public static LazySingleton getInstance() {
if (LAZY_SINGLETON_INSTANCE != null) return LAZY_SINGLETON_INSTANCE; LAZY_SINGLETON_INSTANCE = new LazySingleton();
return LAZY_SINGLETON_INSTANCE; } /**
* 4、要想稍微安全就加synchronized同步锁
* 但是:此种方式因为把synchronized加在了方法上,导致所有访问争锁而出现 资源的浪费
*/
/* public static synchronized lazy_unsafe_singleton getInstance() {
if (LAZY_UNSAFE_INSTANCE_SINGLETON != null) return LAZY_UNSAFE_INSTANCE_SINGLETON; LAZY_UNSAFE_INSTANCE_SINGLETON = new Singleton_lazy_unsafe();
return LAZY_UNSAFE_INSTANCE_SINGLETON; }*/
}

3.1.1.3、饿汉模式

定义:体现在“饿”字上,即:一开始就初始化

package com.zixieqing.o3hunger;

/**
* <p>@description : 该类功能 饿汉式实现
* 这种方式和利用static的方式是异曲同工的
* </p>
* <p>@package : com.zixieqing.o3hunger</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class HungerSingleton { private static HungerSingleton HUNGER_SINGLETON_INSTANCE = new HungerSingleton(); private HungerSingleton() {} /**
* 这种方式其实也不安全
* 因为当多线程在if判断时如果在同一时刻二者都判断成立,就会创建不同的实例
*/
public static HungerSingleton getInstance() {
if (HUNGER_SINGLETON_INSTANCE != null) return HUNGER_SINGLETON_INSTANCE; HUNGER_SINGLETON_INSTANCE = new HungerSingleton();
return HUNGER_SINGLETON_INSTANCE;
}
}

3.1.1.4、内部类

package com.zixieqing.o4innerclass;

/**
* <p>@description : 该类功能 使用内部类实现 - 推荐的一种</p>
* <p>@package : com.zixieqing.o4innerclass</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class InnerClassSingleton { /**
* 1、private的构造
*/
private InnerClassSingleton() {} /**
* 2、private static的内部类
* 巧妙之处:调用这个类才会初始化,也就才能获得InnerClassSingleton实例对象
* 同时里面的属性使用static修饰,则:做到线程安全,也巧妙借助了第一种实现方式:使用static的形式
*/
private static class NewInstance{
public static InnerClassSingleton INSTANCE = new InnerClassSingleton();
} /**
* 3、public static对外提供获取当前类实例的方法
*/
public static InnerClassSingleton getInstance() {
return NewInstance.INSTANCE;
}
}

3.1.1.5、双重锁验证

package com.zixieqing.o5twinlock;

/**
* <p>@description : 该类功能 双重锁校验(线程安全)</p>
* <p>@package : com.zixieqing.o5twinlock</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class TwinLockSingleton { private static TwinLockSingleton INSTANCE; private TwinLockSingleton() {} public static TwinLockSingleton getInstance() { if (null != INSTANCE) return INSTANCE; // 双重验证:synchronized 和 if
synchronized (TwinLockSingleton.class) {
if (null == INSTANCE) return INSTANCE = new TwinLockSingleton();
}
return INSTANCE;
}
}

3.1.1.6、CAS

package com.zixieqing.o6cas;

import java.util.concurrent.atomic.AtomicReference;

/**
* <p>@description : 该类功能 利用CAS算法实现
* 好处:CAS的忙等算法是靠底层硬件,所以:保证了线程安全 和 不会产生线程的切换和阻塞的开销,从而提高性能
* 并且:可以支持较大的并发性
* </p>
* <p>@package : com.zixieqing.o6cas</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class CASSingleton { private static CASSingleton CAS_SINGLETON_INSTANCE; /**
* AtomicReference<K> 原子引用 保存“一个”K实例
*/
private static final AtomicReference<CASSingleton> INSTANCE = new AtomicReference<>(); public static CASSingleton getInstance() { /*
缺点就在这里:CAS的忙等 从而造成:如果一直没有获取就会处于死循坏当中
*/
while (true) {
CAS_SINGLETON_INSTANCE = INSTANCE.get();
if (null != CAS_SINGLETON_INSTANCE) return CAS_SINGLETON_INSTANCE;
/*
boolean compareAndSet(V expect, V update)
expect 预期值
update 要改成的新值
如果当前值和预期值相等,那么就以原子的方式将值改为新值 下列逻辑:期望INSTANCE是null,所以将INSTANCE的值改为new Singleton_CAS()
*/
INSTANCE.compareAndSet(null, new CASSingleton());
// 获取INSTANCE的值 返回值就是AtomicReference<Singleton_CAS>中的泛型类型
return INSTANCE.get();
}
}
}

3.1.1.6、枚举

package com.zixieqing.o7num;

/**
* <p>@description : 该类功能 使用枚举来实现(极度推荐)
* </p>
* <p>@package : com.zixieqing.o7num</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public enum EnumSingleton { /**
* 对象
*/
INSTANCE; /**
* 根据需要自行配置getter也行
*/
}

3.1.2、原型模式

定义:根据一个已有对象(原型实例) 创建 新的对象(就是克隆)

解决的问题:创建重复对象,而这部分对象本身比较复杂,生成过程从库或者RPC接口中获取数据的耗时时长可能会很长,因此:采用克隆的方式会节省时间,总之:当系统中需要创建相同或相似的对象时,就可以用原型模式

场景(在开发中貌似都没用到过):

1、CV大师(ctrl+cctrl+v
2、JavaSE中的Object.clone()

了解两个名词:浅拷贝和深拷贝

浅拷贝

1、当类的成员变量是基本数据类型时,浅拷贝会将原对象的属性值赋值给新对象
2、当类中成员变量是引用数据类型时,浅拷贝 会将 原对象的引用数据类型的地址 赋值给新对象的成员变量。也就是说 两个对象共享了同一个数据。当其中一个对象修改成员变量的值时,另外一个的值也会随之改变
深拷贝
无论是 基本数据类型还是引用数据类型,都会去开辟额外的空间给新对象

3.1.2.1、用Object.clone() API(不推荐)

3.1.2.1.1、克隆基本数据类型

浅拷贝会将原对象的属性值赋值给新对象(拷贝的是值)

注:String底层被final修饰了的,修改值之后是重新创建了一个Sting对象,修改之后不会影响原对象

package com.zixieqing;

/**
* <p>@description : 该类功能 原型类(属性都是基本数据类型时)
* Cloneable 标志Object.clone()方法可以对Person该类的实例进行字段的复制
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class Person implements Cloneable{ private String name; public Person() {
} public Person(String name) {
this.name = name;
} @Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
} @Override
protected Object clone() throws CloneNotSupportedException {
Person person = null;
try {
person = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}
package com.zixieqing;

/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : MeiChengsong</p>
* <p>@version : V1.0.0</p>
*/ public class Test { public static void main(String[] args) throws CloneNotSupportedException { Person person = new Person();
person.setName("紫邪情"); Person clonePerson = (Person) person.clone();
System.out.println( "原型对象:" + person);
System.out.println( "克隆对象:" + clonePerson); clonePerson.setName("小紫"); System.out.println("==========修改之后=============");
System.out.println(person);
System.out.println(clonePerson); }
}

3.1.2.1.2、克隆引用数据类型

浅拷贝 会将 原对象的引用数据类型的地址 赋值给新对象的成员变量(拷贝的是地址)。也就是说 两个对象共享了同一个数据。当其中一个对象修改成员变量的值时,另外一个的值也会随之改变

package com.zixieqing;

/**
* <p>@description : 该类功能 原型类(属性是引用数据类型时)
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class Peron2 implements Cloneable{
private Person person; public Peron2() {
} public Peron2(Person person) {
this.person = person;
} @Override
protected Object clone() throws CloneNotSupportedException {
Peron2 peron2 = null; try {
peron2 = (Peron2) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
} return peron2;
} @Override
public String toString() {
return "Peron2{" +
"person=" + person +
'}';
} public Person getPerson() {
return person;
} public void setPerson(Person person) {
this.person = person;
}
}
package com.zixieqing;

/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : MeiChengsong</p>
* <p>@version : V1.0.0</p>
*/ public class Test { public static void main(String[] args) throws CloneNotSupportedException { Peron2 peron2 = new Peron2();
Person person = new Person("法外狂徒");
peron2.setPerson(person); Peron2 clonePerson2 = (Peron2) peron2.clone(); System.out.println(peron2);
System.out.println(clonePerson2); person.setName("张三");
clonePerson2.setPerson(person); System.out.println("修改之后");
System.out.println(peron2);
System.out.println(clonePerson2);
}
}

3.1.2.2、使用序列化(推荐)

package com.zixieqing.o2useserialize;

import com.zixieqing.o1useclone.Person;

import java.io.*;

/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing.o2useserialize</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class Test { public static void main(String[] args) throws IOException { Person3 person3 = new Person3(0, new Person("紫邪情"));
Person3 cloneInstance = cloneInstance(person3); System.out.println(person3);
System.out.println(cloneInstance); cloneInstance.setSex(1); System.out.println("===========修改之后============="); System.out.println(person3);
System.out.println(cloneInstance);
} /**
* <p>@description : 该方法功能 对象序列化克隆
* </p>
* <p>@methodName : cloneInstance</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
* @param person3 要进行序列化克隆的对象
* @return com.zixieqing.o2useserialize.Person3
*/
private static Person3 cloneInstance(Person3 person3) throws IOException {
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis;
ObjectInputStream ois;
Person3 person = null; try {
// 序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(person3); bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
person = (Person3) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != bos) bos.close(); if (null != oos) oos.close();
} return person;
}
}

3.1.3、工厂模式

解决的问题:就是平时写代码时,new实例对象的情况,因此:要是需要new实例对象时就可以考虑是否采用工厂模式,从而提高扩展性,减少以后的改代码量

3.1.3.1、简单工厂(工厂基础)

定义:把对类的创建初始化全都交给一个工厂来执行,而用户不需要关心创建的过程是什么样的,只需要告诉工厂,我想要什么就行了

设计模式中并没有所谓的简单工厂,这玩意儿严格来说是一种编码规范,但是:也是学工厂模式的基础

简单工厂的角色

抽象产品:定义产品的规则,即产品有哪些特性和功能,可以是接口、抽象类、普通类也行(但一般不会这么干)
具体产品:实现或继承抽象产品的子类
产品工厂:提供创建产品的方法,让使用者通过该方法获取产品

3.1.3.1.1、简单逻辑

开发场景:网上买商品,假设有三种购买方式(前面的数字对应其类型):1、通过优惠卡;2、通过快播兑换卡;3、啥也没用,直接购买实物商品,根据前面说的三角色来整活

准备工作:依赖导入

        <dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- LOGGING begin -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.9</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>

1、抽象产品:定义规则

package com.zixieqing.o1simplefactory.o1simplelogic;

/**
* <p>@description : 该类功能 抽象产品:购物
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o1simplelogic</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public interface IShopping { /**
* <p>@description : 该方法功能 发送商品
* </p>
* <p>@methodName : sendCommodity</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
* @param userId 用户id
* @param goodsName 商品名
*/
void sendCommodity(String userId, String goodsName) throws Exception;
}

2、具体产品:实现或继承抽象产品的子类

优惠卡方式


package com.zixieqing.o1simplefactory.o1simplelogic.impl; import com.zixieqing.o1simplefactory.o1simplelogic.IShopping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* <p>@description : 该类功能 具体商品:优惠卡
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o1simplelogic.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class CouponService implements IShopping { private Logger logger = LoggerFactory.getLogger(CouponService.class); @Override
public void sendCommodity(String userId, String goodsName) throws Exception {
logger.info("用户:{},通过优惠卡xxxxxx,购买了:{}", userId, goodsName);
}
}

快播兑换卡方式


package com.zixieqing.o1simplefactory.o1simplelogic.impl; import com.zixieqing.o1simplefactory.o1simplelogic.IShopping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* <p>@description : 该类功能 具体商品:快播兑换卡
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o1simplelogic.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class QvodCardService implements IShopping { private Logger logger = LoggerFactory.getLogger(QvodCardService.class); @Override
public void sendCommodity(String userId, String goodsName) throws Exception {
logger.info("用户:{},通过快播兑换卡yyyyy,购买了:{}", userId, goodsName);
}
}

实物够买的方式


package com.zixieqing.o1simplefactory.o1simplelogic.impl; import com.zixieqing.o1simplefactory.o1simplelogic.IShopping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* <p>@description : 该类功能 具体商品:啥也不用,直接实物购买
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o1simplelogic.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class GoodsService implements IShopping { private Logger logger = LoggerFactory.getLogger(GoodsService.class); @Override
public void sendCommodity(String userId, String goodsName) throws Exception {
logger.info("用户:{},实物购买了:{}", userId, goodsName);
}
}

3、产品工厂:提供创建产品的方法,让调用者通过该工厂获取产品

package com.zixieqing.o1simplefactory.o1simplelogic;

import com.zixieqing.o1simplefactory.o1simplelogic.impl.CouponService;
import com.zixieqing.o1simplefactory.o1simplelogic.impl.GoodsService;
import com.zixieqing.o1simplefactory.o1simplelogic.impl.QvodCardService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* <p>@description : 该类功能 产品工厂:购物工厂
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o1simplelogic</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class ShoppingFactory { private Logger logger = LoggerFactory.getLogger(ShoppingFactory.class); /**
* <p>@description : 该方法功能 购物
* </p>
* <p>@methodName : shopping</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
* @param type 购物方式 1、优惠卡;2、快播兑换卡;3、实物购买
* @return com.zixieqing.o1simplefactory.o1simplelogic.IShopping
*/
public IShopping shopping(Integer type) {
if (null == type) return null; logger.info("正在拣货....."); if (1 == type) return new CouponService(); if (2 == type) return new QvodCardService(); if (3 == type) return new GoodsService(); throw new RuntimeException("不存在的商品服务类型");
}
}

注:如果上面这个工厂的方法加上了static就变成了静态工厂(静态方法能被继承、但不能被重写)

4、测试:调用者通过工厂获取对应产品

package com.zixieqing;

import com.zixieqing.o1simplefactory.o1simplelogic.IShopping;
import com.zixieqing.o1simplefactory.o1simplelogic.ShoppingFactory; /**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class APITest { public static void main(String[] args) throws Exception{ ShoppingFactory shoppingFactory = new ShoppingFactory(); // 1、优惠卡类型
IShopping shopping_1 = shoppingFactory.shopping(1);
shopping_1.sendCommodity(System.nanoTime()+"", "充气娃娃"); System.out.println("================华丽的分割线==================="); // 2、快播兑换卡
IShopping shopping_2 = shoppingFactory.shopping(2);
shopping_2.sendCommodity(System.nanoTime()+"", "AI女票"); System.out.println("================华丽的分割线==================="); IShopping shopping_3 = shoppingFactory.shopping(3);
shopping_3.sendCommodity(System.nanoTime()+"", "枸杞"); }
}

上面就是理解简单工厂的逻辑,总结一丢丢

简单工厂的逻辑:

由上图也可知,简单工厂优点就是调用者可以免除直接创建产品对象的责任,而仅仅"消费"产品,明确责任边界,降低耦合性,当然其缺点也很明显
1、违背了开闭原则

所以从上图可知:简单工厂就是横向发展(不断加实现类、工厂类中不断加逻辑判断)

3.1.3.1.2、更加贴合开发场景的逻辑

1、先决条件:先简单搞个返回结果集的工具类

package com.zixieqing.o1simplefactory.o2complex.util;

/**
* <p>@description : 该类功能 返回结果集工具类
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o2complex.util</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class ResultUtil { /**
* 编码
*/
private String code; /**
* 信息
*/
private String info; public ResultUtil(String code, String info) {
this.code = code;
this.info = info;
} public String getCode() {
return code;
} public void setCode(String code) {
this.code = code;
} public String getInfo() {
return info;
} public void setInfo(String info) {
this.info = info;
}
}

2、优惠卡服务

package com.zixieqing.o1simplefactory.o2complex.coupon;

import com.zixieqing.o1simplefactory.o2complex.util.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* <p>@description : 该类功能 模拟发放优惠券业务
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o2complex.coupon</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class CouponService { private Logger logger = LoggerFactory.getLogger(CouponService.class); /**
* <p>@description : 该方法功能 发放优惠券
* </p>
* <p>@methodName : sendCoupon</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
* @param uId 用户id
* @param couponNumber 分配的优惠券号码
* @param uuid 随机生成的uuid号
* @return com.zixieqing.o1simplefactory.o2complex.util.ResultUtil
*/
public ResultUtil sendCoupon(String uId, String couponNumber, String uuid) {
logger.info("发放优惠券业务准备启动.........."); logger.info("用户:{},获得了优惠券:{}", uId, uId + couponNumber + uuid); return new ResultUtil("0000", "优惠券发放成功");
}
}

3、快播兑换卡业务

package com.zixieqing.o1simplefactory.o2complex.qvod;

import com.zixieqing.o1simplefactory.o2complex.util.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* <p>@description : 该类功能 快播兑换卡业务
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o2complex.qvod</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class QvodService { private Logger logger = LoggerFactory.getLogger(QvodService.class); /**
* <p>@description : 该方法功能 授予兑换卡
* </p>
* <p>@methodName : grentToken</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
* @param phone 用户手机号
* @param cardId 随机生成的卡号
* @return com.zixieqing.o1simplefactory.o2complex.util.ResultUtil
*/
public ResultUtil grentToken(String phone, String cardId) {
logger.info("授予的兑换卡为:{}", phone + cardId);
return new ResultUtil("0000", phone + cardId);
}
}

4、实物购买商品业务

辅助对象


package com.zixieqing.o1simplefactory.o2complex.goods; /**
* <p>@description : 该类功能 实物购买:支付要求对象
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o2complex.goods</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class DeliverRequest { /**
* 用户姓名
*/
private String userName;
/**
* 用户手机
*/
private String userPhone;
/**
* 商品SKU:库存保有单位
*/
private String sku;
/**
* 订单ID
*/
private String orderId;
/**
* 收货人姓名
*/
private String consigneeUserName;
/**
* 收货人手机
*/
private String consigneeUserPhone;
/**
* 收获人地址
*/
private String consigneeUserAddress; public String getUserName() {
return userName;
} public void setUserName(String userName) {
this.userName = userName;
} public String getUserPhone() {
return userPhone;
} public void setUserPhone(String userPhone) {
this.userPhone = userPhone;
} public String getSku() {
return sku;
} public void setSku(String sku) {
this.sku = sku;
} public String getOrderId() {
return orderId;
} public void setOrderId(String orderId) {
this.orderId = orderId;
} public String getConsigneeUserName() {
return consigneeUserName;
} public void setConsigneeUserName(String consigneeUserName) {
this.consigneeUserName = consigneeUserName;
} public String getConsigneeUserPhone() {
return consigneeUserPhone;
} public void setConsigneeUserPhone(String consigneeUserPhone) {
this.consigneeUserPhone = consigneeUserPhone;
} public String getConsigneeUserAddress() {
return consigneeUserAddress;
} public void setConsigneeUserAddress(String consigneeUserAddress) {
this.consigneeUserAddress = consigneeUserAddress;
}
}

业务

  package com.zixieqing.o1simplefactory.o2complex.goods;

  import com.alibaba.fastjson.JSON;
import com.zixieqing.o1simplefactory.o2complex.util.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* <p>@description : 该类功能 实物购买商品业务
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o2complex.goods</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class GoodsService { private Logger logger = LoggerFactory.getLogger(GoodsService.class); /**
* <p>@description : 该方法功能 发货
* </p>
* <p>@methodName : deliverGoods</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
* @param deliverRequest 辅助:支付对象
* @return com.zixieqing.o1simplefactory.o2complex.util.ResultUtil
*/
public ResultUtil deliverGoods(DeliverRequest deliverRequest) {
logger.info("模拟发送实物商品一个:{}", JSON.toJSONString(deliverRequest));
return new ResultUtil("0000", "发货成功:" + deliverRequest);
}
}

5、抽象产品:定义规则

package com.zixieqing.o1simplefactory.o2complex;

import java.util.Map;

/**
* <p>@description : 该类功能 商品
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o2complex</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public interface ICommodityService { /**
* <p>@description : 该方法功能 发送商品
* </p>
* <p>@methodName : sendCommodity</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
* @param uId 用户id
* @param commodityId 商品id
* @param bizId 业务id
* @param extMap 扩展信息
*/
void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception;
}

6、具体商品:实现或继承抽象产品的子类

优惠卡

  package com.zixieqing.o1simplefactory.o2complex.impl;

  import com.zixieqing.o1simplefactory.o2complex.ICommodityService;
import com.zixieqing.o1simplefactory.o2complex.coupon.CouponService;
import com.zixieqing.o1simplefactory.o2complex.util.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.Map;
import java.util.UUID; /**
* <p>@description : 该类功能 TODO
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o2complex.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class CouponCommodityServiceImpl implements ICommodityService { private Logger logger = LoggerFactory.getLogger(CouponCommodityServiceImpl.class); /**
* 模拟@autowried注入
*/
private CouponService couponService = new CouponService(); @Override
public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
ResultUtil result = couponService.sendCoupon(uId, commodityId, UUID.randomUUID().toString()); if (!"0000".equals(result.getCode()))
throw new RuntimeException(result.getInfo());
}
}

快播兑换卡

  package com.zixieqing.o1simplefactory.o2complex.impl;

  import com.zixieqing.o1simplefactory.o2complex.ICommodityService;
import com.zixieqing.o1simplefactory.o2complex.qvod.QvodService;
import com.zixieqing.o1simplefactory.o2complex.util.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.Map; /**
* <p>@description : 该类功能 快播兑换卡发货
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o2complex.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class QvodCommodityServiceImpl implements ICommodityService { private Logger logger = LoggerFactory.getLogger(CouponCommodityServiceImpl.class); /**
* 模拟注入
*/
private QvodService qvodService = new QvodService(); @Override
public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
// 这里把电话号码定死,模拟而已
ResultUtil result = qvodService.grentToken("12345678910", bizId + commodityId); logger.info("通过快播兑换卡:{},获取商品:{}", bizId + commodityId, commodityId); if (!"0000".equals(result.getCode()))
throw new RuntimeException(result.getInfo());
}
}

实物购买

  package com.zixieqing.o1simplefactory.o2complex.impl;

  import com.zixieqing.o1simplefactory.o2complex.ICommodityService;
import com.zixieqing.o1simplefactory.o2complex.goods.DeliverRequest;
import com.zixieqing.o1simplefactory.o2complex.goods.GoodsService;
import com.zixieqing.o1simplefactory.o2complex.util.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.util.Map; /**
* <p>@description : 该类功能 实物购买商品
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o2complex.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class GoodsCommodityServiceImpl implements ICommodityService { private Logger logger = LoggerFactory.getLogger(GoodsCommodityServiceImpl.class); /**
* 模拟注入
*/
private GoodsService goodsService = new GoodsService(); @Override
public void sendCommodity(String uId, String commodityId, String bizId, Map<String, String> extMap) throws Exception {
DeliverRequest deliverRequest = new DeliverRequest();
// 下面这些信息去数据库搞出来
deliverRequest.setUserName("紫邪情");
deliverRequest.setUserPhone("123143124342");
deliverRequest.setSku(commodityId);
deliverRequest.setOrderId(bizId);
deliverRequest.setConsigneeUserName(extMap.get("consigneeUserName"));
deliverRequest.setConsigneeUserPhone(extMap.get("consigneeUserPhone"));
deliverRequest.setConsigneeUserAddress(extMap.get("consigneeUserAddress")); ResultUtil result = goodsService.deliverGoods(deliverRequest); if (!"0000".equals(result.getCode()))
throw new RuntimeException(result.getInfo());
}
}

7、产品工厂:提供创建产品的方法,让调用者通过该方法获取产品

package com.zixieqing.o1simplefactory.o2complex;

import com.zixieqing.o1simplefactory.o2complex.impl.CouponCommodityServiceImpl;
import com.zixieqing.o1simplefactory.o2complex.impl.GoodsCommodityServiceImpl;
import com.zixieqing.o1simplefactory.o2complex.impl.QvodCommodityServiceImpl; /**
* <p>@description : 该类功能 产品工厂
* </p>
* <p>@package : com.zixieqing.o1simplefactory.o2complex</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class CommodityFactory { /**
* <p>@description : 该方法功能 获取产品
* </p>
* <p>@methodName : getCommodity</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
* @param type 产品类型
* @return com.zixieqing.o1simplefactory.o2complex.ICommodityService
*/
public ICommodityService getCommodity(Integer type) {
if (1 == type) return new CouponCommodityServiceImpl(); if (2 == type) return new QvodCommodityServiceImpl(); if (3 == type) return new GoodsCommodityServiceImpl(); throw new RuntimeException("不合法的商品类型");
}
}

8、测试

package com.zixieqing;

import com.zixieqing.o1simplefactory.o1simplelogic.IShopping;
import com.zixieqing.o1simplefactory.o1simplelogic.ShoppingFactory;
import com.zixieqing.o1simplefactory.o2complex.CommodityFactory;
import com.zixieqing.o1simplefactory.o2complex.ICommodityService; import java.util.UUID; /**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class APITest { public static void main(String[] args) throws Exception{ System.out.println("================华丽的分割线:简单结合业务开发进行测试==================="); CommodityFactory commodityFactory = new CommodityFactory(); // 1、优惠卡类型
ICommodityService commodity_1 = commodityFactory.getCommodity(1);
commodity_1.sendCommodity(System.nanoTime() + "",
System.currentTimeMillis() + "",
UUID.randomUUID() + "",
null); // 2、快播兑换卡
ICommodityService commodity_2 = commodityFactory.getCommodity(2);
commodity_2.sendCommodity(System.nanoTime() + "",
System.currentTimeMillis() + "",
UUID.randomUUID() + "",
null); // 3、实物购买
ICommodityService commodity_3 = commodityFactory.getCommodity(3);
HashMap<String, String> extMap = new HashMap<>();
extMap.put("consigneeUserName", "紫邪情");
extMap.put("consigneeUserPhone", "31343214321432");
extMap.put("consigneeUserAddress", "浙江省.杭州市.余杭区.XX街道.YY小区.324134321431");
commodity_3.sendCommodity(System.nanoTime() + "",
System.currentTimeMillis() + "",
UUID.randomUUID() + "",
extMap);
}
}

3.1.3.1.3、构建出简单工厂的方式

废话文学

1、思考要创建的几个实例(具体产品)之间有什么共通性
2、将多个实例的共通性抽取成一个接口(抽象产品)
3、使用一个工厂来对创建的实例进行判断,从而让调用者根据条件得到想要的实例对象(还可以结合反射来创建对象)
4、然后根据下图进行架构即可

3.1.3.2、工厂方法模式

定义:定义一个创建对象的接口,但由子类(具体工厂)决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类

废话文学:

工厂方法模式的角色

抽象工厂角色:工厂方法模式的核心,是具体工厂角色必须实现的接口或者继承的父类
具体工厂角色:含有和具体业务逻辑有关的代码,由业务调用创建对应的具体产品的对象
抽象产品角色:是具体产品继承的父类或者是实现的接口
具体产品角色:具体工厂角色所创建的对象就是此角色的实例

工厂方法模式这玩意儿其实就是在简单工厂的基础上稍微变了一下而已,多了一层罢了(没什么是加一层解决不了的,一层不行,那就再来一层),这里加的这一层就是对业务层再抽象了一下而已

简单工厂是横向发展(不断横向添加实现类),而工厂方法模式本质是为了解决简单工厂模式的问题(违背开闭原则),所以优点和简单工厂模式一样,其是纵向发展(不断纵向添加工厂类+实现类)

简单工厂模式

工厂方法模式:

从上图可以看出:工厂方法模式做的是同一类产品(一条流水线开机时不会还搞多产品加工吧),这个点就是和下面抽象工厂的最大区别(抽象工厂针对的是产品簇)

3.1.3.2.1、Java中使用工厂方法模式的地方

这个东西其实在刚刚前面的代码中有一个东西就用到了,即:SLF4J日志门面,前面用了一个LoggerFactory,它里面就用到了工厂方法模式

1、进入getLogger()

// 进入getLogger()
private Logger logger = LoggerFactory.getLogger(QvodService.class); // 得到的代码
public static Logger getLogger(Class clazz) {
// 进入这里的getLogger()
return getLogger(clazz.getName());
} // 得到代码
public static Logger getLogger(String name) {
// 再看一下getILoggerFactory()
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
} // getILoggerFactory()的代码
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == 0) {
INITIALIZATION_STATE = 1;
performInitialization();
} switch(INITIALIZATION_STATE) {
case 1:
// 重点1、static SubstituteLoggerFactory TEMP_FACTORY = new SubstituteLoggerFactory();
return TEMP_FACTORY;
case 2:
throw new IllegalStateException("org.slf4j.LoggerFactory could not be successfully initialized. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
case 3:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case 4:
// 重点2、static NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory();
return NOP_FALLBACK_FACTORY;
default:
throw new IllegalStateException("Unreachable code");
}
}

上面的两个:TEMP_FACTORYNOP_FALLBACK_FACTORY都实现了ILoggerFactory

2、看一眼ILoggerFactory

// 这个就是根工厂:定义最大规则的那个叼毛
public interface ILoggerFactory {
Logger getLogger(String var1);
}

逆向回来,逻辑就变成如下的样子

废话文学

在开发中怎么使用工厂方法模式(下列逻辑不止适用于此模式,创建型、行为型、结构型均适合,了解每种类型针对的是对什么做架构即可,如:是对类中方法、还是类本身等等做架构来进行抽离,最后时间长了一看需求就知道咋个设计了)

1、思考自己要创建的几个实例( 即:具体产品)有什么共通性

2、将共通性抽取出来变成一个接口或抽象类(即:抽象产品)

3、思考对每一个实例(产品)弄一个工厂类之后(即:具体工厂),那这几个工厂类之间又有什么共通性

4、将工厂类的共通性抽取出来变成一个接口(即:抽象工厂)

5、当然:再考虑以后应该会在什么地方添加功能、是否会扩展的话更好,但这一条需要经验积累

6、然后使用下图方式做架构即可

3.1.3.2.2、实例

场景:文件解析,假设有两种:1、xml文件解析;2、json文件解析

逻辑如下

1、抽象产品

package com.zixieqing.o2factorymethod;

/**
* <p>@description : 该类功能 文件解析器
* </p>
* <p>@package : com.zixieqing.o2factorymethod</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public interface IFileParser { String parse();
}

2、具体产品

json解析


package com.zixieqing.o2factorymethod.impl; import com.zixieqing.o2factorymethod.IFileParser; /**
* <p>@description : 该类功能 json文件解析器
* </p>
* <p>@package : com.zixieqing.o2factorymethod.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class JsonFileParse implements IFileParser {
@Override
public String parse() {
return "这里就搞json文件解析的逻辑";
}
}

xml解析


package com.zixieqing.o2factorymethod.impl; import com.zixieqing.o2factorymethod.IFileParser; /**
* <p>@description : 该类功能 xml文件解析器
* </p>
* <p>@package : com.zixieqing.o2factorymethod</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class XMLFileParse implements IFileParser {
@Override
public String parse() {
return "这里就搞xml文件的解析逻辑";
}
}

3、抽象工厂

package com.zixieqing.o2factorymethod.factory;

import com.zixieqing.o2factorymethod.IFileParser;

/**
* <p>@description : 该类功能 解析器抽象工厂
* </p>
* <p>@package : com.zixieqing.o2factorymethod</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public interface IParserFactory { IFileParser parse();
}

4、具体工厂

json解析工厂


package com.zixieqing.o2factorymethod.factory.impl; import com.zixieqing.o2factorymethod.IFileParser;
import com.zixieqing.o2factorymethod.factory.IParserFactory;
import com.zixieqing.o2factorymethod.impl.JsonFileParse; /**
* <p>@description : 该类功能 json解析工厂
* </p>
* <p>@package : com.zixieqing.o2factorymethod.factory</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class JsonParseFactory implements IParserFactory { @Override
public IFileParser parse() {
return new JsonFileParse();
}
}

xml解析工厂


package com.zixieqing.o2factorymethod.factory.impl; import com.zixieqing.o2factorymethod.IFileParser;
import com.zixieqing.o2factorymethod.factory.IParserFactory;
import com.zixieqing.o2factorymethod.impl.XMLFileParse; /**
* <p>@description : 该类功能 xml解析工厂
* </p>
* <p>@package : com.zixieqing.o2factorymethod.factory</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class XMLParseFactory implements IParserFactory {
@Override
public IFileParser parse() {
return new XMLFileParse();
}
}

3.1.3.3、抽象工厂模式

定义:在工厂方法模式的基础上,对工厂进行变化一下

解决的问题:产品簇的问题(一个中心工厂来创建其他工厂)

对于产品簇百度有一个通俗易懂的解释:指具有相同或相似的功能结构或性能,共享主要的产品特征、组件或子结构,并通过变型配置来满足特定市场的一组产品的聚类

废话文学对产品簇的解释:产品簇是指工厂生产出的产品们之间彼此具备强关联。比如:AK47工厂生产的 AK47步枪、AK47专配的子弹,一旦 AK47装错了子弹是无法正常开枪的(甚至会炸膛)

抽象工厂模式的角色

抽象工厂角色:工厂方法模式的核心,是具体工厂角色必须实现的接口或者继承的父类
具体工厂角色:含有和具体业务逻辑有关的代码,由业务调用创建对应的具体产品的对象
抽象产品角色:是具体产品继承的父类或者是实现的接口
具体产品角色:具体工厂角色所创建的对象就是此角色的实例

抽象工厂模式逻辑举例(上面的四角色自行对应)

3.1.3.3.1、简单逻辑实现

就用上面说的家具来举例

1、椅子接口(抽象产品)

package com.zixieqing.o3abstractfactory;

/**
* <p>@description : 该类功能 抽象产品:椅子接口
* </p>
* <p>@package : com.zixieqing.o3abstractfactory</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public interface IChair { /**
* <p>@description : 该方法功能 生产椅子
* </p>
* <p>@methodName : createChair</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
*
* @return java.lang.String
*/
String create();
}

1.1、木质桌子(具体产品)

package com.zixieqing.o3abstractfactory.impl;

import com.zixieqing.o3abstractfactory.IChair;

/**
* <p>@description : 该类功能 具体产品:木质椅子
* </p>
* <p>@package : com.zixieqing.o3abstractfactory.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class WoodenChair implements IChair {
@Override
public String create() {
return "木质椅子";
}
}

1.2、塑料桌子(具体产品)

package com.zixieqing.o3abstractfactory.impl;

import com.zixieqing.o3abstractfactory.IChair;

/**
* <p>@description : 该类功能 具体产品:塑料椅子
* </p>
* <p>@package : com.zixieqing.o3abstractfactory.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class PlasticChair implements IChair {
@Override
public String create() {
return "塑料椅子";
}
}

2、桌子接口(抽象产品)

package com.zixieqing.o3abstractfactory;

/**
* <p>@description : 该类功能 抽象产品:桌子接口
* </p>
* <p>@package : com.zixieqing.o3abstractfactory</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public interface IDesk { /**
* <p>@description : 该方法功能 生产桌子
* </p>
* <p>@methodName : create</p>
* <p>@author: ZiXieqing</p>
* <p>@version: V1.0.0</p>
*
* @return java.lang.String
*/
String create(); }

木质桌子

package com.zixieqing.o3abstractfactory.impl;

import com.zixieqing.o3abstractfactory.IDesk;

/**
* <p>@description : 该类功能 具体产品:木质桌子
* </p>
* <p>@package : com.zixieqing.o3abstractfactory.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class WoodenDesk implements IDesk {
@Override
public String create() {
return "木质桌子";
}
}

塑料桌子

package com.zixieqing.o3abstractfactory.impl;

import com.zixieqing.o3abstractfactory.IDesk;

/**
* <p>@description : 该类功能 具体产品:塑料桌子
* </p>
* <p>@package : com.zixieqing.o3abstractfactory.impl</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class PlastidDeck implements IDesk {
@Override
public String create() {
return "塑料桌子";
}
}

3、家具抽象工厂

package com.zixieqing.o3abstractfactory;

/**
* <p>@description : 该类功能 抽象工厂:家具工厂
* </p>
* <p>@package : com.zixieqing.o3abstractfactory</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public interface IFurnitureFactory { IChair createChair(); IDesk createDesk();
}

木质家具工厂

package com.zixieqing.o3abstractfactory.factory;

import com.zixieqing.o3abstractfactory.IChair;
import com.zixieqing.o3abstractfactory.IDesk;
import com.zixieqing.o3abstractfactory.IFurnitureFactory;
import com.zixieqing.o3abstractfactory.impl.WoodenChair;
import com.zixieqing.o3abstractfactory.impl.WoodenDesk; /**
* <p>@description : 该类功能 具体工厂:专门生产木质家具这一产品簇需要的东西
* </p>
* <p>@package : com.zixieqing.o3abstractfactory.factory</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class WoodenIFurnitureFactory implements IFurnitureFactory {
@Override
public IChair createChair() {
return new WoodenChair();
} @Override
public IDesk createDesk() {
return new WoodenDesk();
}
}

塑料家具工厂

package com.zixieqing.o3abstractfactory.factory;

import com.zixieqing.o3abstractfactory.IChair;
import com.zixieqing.o3abstractfactory.IDesk;
import com.zixieqing.o3abstractfactory.IFurnitureFactory;
import com.zixieqing.o3abstractfactory.impl.PlasticChair;
import com.zixieqing.o3abstractfactory.impl.PlastidDeck; /**
* <p>@description : 该类功能 具体工厂:专门生产塑料家具这一产品簇需要的东西
* </p>
* <p>@package : com.zixieqing.o3abstractfactory</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class PlasticFurnitureFactory implements IFurnitureFactory {
@Override
public IChair createChair() {
return new PlasticChair();
} @Override
public IDesk createDesk() {
return new PlastidDeck();
}
}

4、测试

package com.zixieqing;

import com.zixieqing.o3abstractfactory.factory.PlasticFurnitureFactory;
import com.zixieqing.o3abstractfactory.factory.WoodenIFurnitureFactory; /**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class AbstractFactoryTest { public static void main(String[] args) {
// 木质椅子
String chair = new WoodenIFurnitureFactory().createChair().create();
// 木质桌子
String desk = new PlasticFurnitureFactory().createDesk().create(); System.out.println(chair + "+" +desk); // 想要塑料家具这个产品簇的东西,就去找塑料家具工厂即可
}
}

3.1.3.2、工厂方法与抽象工厂模式的区别

废话文学

1、首先两个模式都可以生产产品 / 创建实例

2、其次看前面的定义:

工厂方法针对的是“某种产品”,继而成为“某种产品A” 对应 “某工厂A”
抽象工厂针对的是“某类产品”,继而变成“某工厂B” 生产 “某类产品”(可以得到这类产品的实例,也可以得到这类产品的零部件)

3、情景理解:

工厂方法模式就像华为手机专卖店一样,卖的是这一种手机
抽象工厂模式就像手机售卖店一样,可以买到华为、小米等手机,同时也可以根据自己需要买到自己手机的零部件

4、在场景上(注意看下图,两个很像的,但注意看下图中抽象工厂、抽象产品这两个地方):

工厂方法模式:它是让某一条产品线上的产品更易被扩展(要扩展就是下图加工厂和实现类呗)

抽象工厂模式:它是让多条产品线中,既保证这多条产品线中的单条产品线的产品更易被扩展的同时,又保证多条产品线之间遵循着同样的约束

3.1.4、建造者模式

定义:将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示;另外:建造者模式又叫生成器模式

换言之:将多个简单对象通过一步一步组装,最后变成复杂对象的过程;同时:简单对象之间的组装(相同的物料)通过相似的组装过程可以得到不一样的复杂对象(即:相同 / 相似的物料,不同的繁琐组装,得到不同的对象[此对象是一个复杂对象组合起来的])

典型例子:计算机有显示器、鼠标、键盘等等部件,不同用户想要的电脑样子、配置不一样,但都只需要和售卖员讲一下自己要的,最后店铺就会组装好用户想要的电脑给用户

注意:核心点是“相同 或 相似的组成成分”,即:创建对象需要很多步骤,但是步骤的顺序不一定固定,这种场景才是使用建造者模式的最佳场景(开发经典例子:一个类的内部结构很复杂,如有很多属性时,就可以考虑用建造者模式改造一下)

建造者角色

产品(Product):就是最终要创建的产品对象
抽象建造者(Builder):规范产品对象的各个组成部分的建造;可以是接口或抽象类;相当于:老板,专门约束下级的
具体建造者(ConcreteBuilder):实现或继承抽象建造者的子类;是创建产品对象的执行者;相当于:真正干活的人
指挥者(Director):负责产品对象的构建次序(注:这个叼毛可有可无),就相当于:有图纸的工程师;它和抽象建造者是关联关系(即:这里面的方法返回值类型是抽象建造者类型)
可有可无的原因:调用者万一就是个懂行的,那还要专门找个看图纸的干嘛,直接上场指挥产品应该按什么顺序构建就可以了
由于这个指挥者可有可无,所以这个建造者模式的写法相当灵活,建造者模式的核心是抽象建造者和具体建造者,其他的随意变(结合其他设计模式、结合自己认为不影响逻辑的设计都可以),要么接口+实现类,要么抽象类+继承类,但是也得注意:抽象建造者里面的成员需要满足的条件,即:方法或属性的设计(换言之:就是依赖倒置原则,只是对属性或方法进行了要求,从而做到让对象的构建独立(产品对象的属性构建顺序:可以通过指挥者、也可以直接完全交给调用者),表示分离(直接交给具体构建者,最后得到一个产品对象即可)

3.1.4.1、简单逻辑

3.1.4.1.1、有指挥者的情况

注:以下逻辑不是固定的,可以随意变化

大体类图如下

1-1、产品对象

package com.zixieqing.o1simple;

/**
* <p>@description : 该类功能 产品对象:电脑
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class Computer { /**
* 电脑主机
*/
private String host; /**
* 电脑显示器
*/
private String monitor; /**
* 扩展信息
*/
private String extendMap; @Override
public String toString() {
return "Computer{" +
"host='" + host + '\'' +
", monitor='" + monitor + '\'' +
", extendMap='" + extendMap + '\'' +
'}';
} public void setHost(String host) {
this.host = host;
} public void setMonitor(String monitor) {
this.monitor = monitor;
} public void setExtendMap(String extendMap) {
this.extendMap = extendMap;
}
}

1-2、抽象建造者

package com.zixieqing.o1simple.builder;

import com.zixieqing.o1simple.Computer;

/**
* <p>@description : 该类功能 抽象建造者:老板 约束下级
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public interface IComputerBuilder { IComputerBuilder buildHost(); IComputerBuilder buildMonitor(); IComputerBuilder buildExtendMap(); Computer getComputer();
}

1-3、具体建造者

package com.zixieqing.o1simple.builder;

import com.zixieqing.o1simple.Computer;

/**
* <p>@description : 该类功能 具体建造者:干活的员工
* </p>
* <p>@package : com.zixieqing.o1simple.builder</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class ComputerBuilder implements IComputerBuilder{ /**
* 组合产品对象
*/
private Computer computer = new Computer(); /**
* 产品对象零部件:建造主机
*/
@Override
public ComputerBuilder buildHost() {
computer.setHost("主机配置");
/*
返回this就是成为链式调用的关键,如:
computerBuilder.buildMonitor()
.buildHost()
.buildExtendMap()
.getComputer();
*/
return this;
} /**
* 产品对象零部件:显示器类型
*/
@Override
public ComputerBuilder buildMonitor() {
computer.setMonitor("显示器类型");
return this;
} /**
* 产品对象零部件:额外信息
*/
@Override
public ComputerBuilder buildExtendMap() {
computer.setExtendMap("额外信息");
return this;
} /**
* 返回构建好的复杂对象 / 产品对象
*/
@Override
public Computer getComputer() {
return computer;
}
}

1-4、指挥者

package com.zixieqing.o1simple.builder;

import com.zixieqing.o1simple.Computer;

/**
* <p>@description : 该类功能 指挥者:拿图纸的工程师,负责产品对象的构建顺序
* </p>
* <p>@package : com.zixieqing.o1simple.builder</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class ComputerDirector { private static IComputerBuilder computerBuilder = new ComputerBuilder(); public static Computer getComputer() {
// 1、先搞显示器
return computerBuilder.buildMonitor()
// 在搞主机配置(这里能够进行链式调用,就是因为具体建造者中建造各部件(属性)时使用了return this的原因)
.buildHost()
// 最后弄其他配置信息
.buildExtendMap()
.getComputer();
}
}

1-5、测试

package com.zixieqing;

import com.zixieqing.o1simple.builder.ComputerDirector;

/**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class APITest { public static void main(String[] args) { System.out.println(ComputerDirector.getComputer());
}
}

1-6、结果

Computer{host='主机配置', monitor='显示器类型', extendMap='额外信息'}

3.1.4.1.2、没有指挥者的情况

有的时候会面临一种情况

上面这个指挥者中的代码多此一举了,这种应该直接交给调用者就行了,由调用者自己来安排产品对象的构建顺序是怎么样的,因此:稍微变一下,把指挥者去掉,然后调用者直接去找建造者即可(抽象建造者、具体建造者代码不变,直接去掉指挥者),那调用就变成如下的样子

package com.zixieqing;

import com.zixieqing.o1simple.Computer;
import com.zixieqing.o1simple.builder.ComputerBuilder;
import com.zixieqing.o1simple.builder.ComputerDirector;
import com.zixieqing.o1simple.builder.IComputerBuilder; /**
* <p>@description : 该类功能 测试
* </p>
* <p>@package : com.zixieqing</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public class APITest { public static void main(String[] args) { IComputerBuilder computerBuilder = new ComputerBuilder(); Computer computer = computerBuilder.buildHost()
.buildExtendMap()
.buildMonitor()
.getComputer(); System.out.println(computer);
}
}

看到上面这个样子是不是感觉熟悉了,就是JDK中StringBuilderappend()这个API的设计模式,当然:要是使用过MyBatis的xml开发模式,那里面有一个使用SqlSessionFactoryBuilder来获取SqlSessionFactory,这里也用到了这个设计模式,甚至Spring底层的BeanDefinitionBuilder通过getBeanDefinition获取BeanDefinition对象也用到了

        StringBuilder builder = new StringBuilder();

        builder.append("张三")
.append("李四")
.append("大刀王五")
.append("王麻子");

看到上面这种是自己传值进去的,所以抽象建造者就又可以变一下了呗(产品对象的各部件对象的构建,值优调用者传进去),如:

package com.zixieqing.o1simple.builder;

import com.zixieqing.o1simple.Computer;

/**
* <p>@description : 该类功能 抽象建造者:老板 约束下级
* </p>
* <p>@package : com.zixieqing.o1simple</p>
* <p>@author : ZiXieqing</p>
* <p>@version : V1.0.0</p>
*/ public interface IComputerBuilder { // 这样甩手掌柜就可以甩得稍微彻底点了
IComputerBuilder buildHost(String host); IComputerBuilder buildMonitor(String monitor); IComputerBuilder buildExtendMap(String extendMap); Computer getComputer();
}

甚至说:要是是类似于SpringBoot的自动转配呢?需要的一些东西在配置文件中,可不可以结合工厂模式去进行加载呢(单例模式随便一个地方都可以扔进去)?

4、说明

原本是打算将设计模式全部也放在这篇博文中的,但是内容太多了,就只放创建型设计模式就已经快4000行了

造成的结果就是:自己新增内容卡顿,弄起来难受得很,而且内容太多加载起来也慢,所以直接决定将设计模式篇章单独弄出来,后续发布

UML建模语言、设计原则、设计模式的相关教程结束。

《UML建模语言、设计原则、设计模式.doc》

下载本文的Word格式文档,以方便收藏与打印。