设计模式

UML

类图

参考文章:https://cloud.tencent.com/developer/article/1012684

箭头方向

子类指向父类

  • 定义子类时需要通过extends关键字指定父类
  • 子类一定是知道父类定义的,但父类并不知道子类的定义
  • 只有知道对方信息时才能指向对方
  • 所以箭头方向是从子类指向父类

继承(泛化)

三角箭头-实线

实现

三角箭头-虚线

依赖

箭头-虚线

一个类用到了另一个类。这种使用关系是具有偶然性的、临时性的、非常弱的

1
2
3
4
5
6
7
8
public class Student {
public void study(Book book){
System.out.println("看书");
}
}

public class Book {
}

关联

箭头-实线

关联关系,即强调了“使用”,例如人和车的关系

idea插件

聚合

空心菱形

更强调整体与部分,整体与部分不是同生共死的

数字的含义

  • 0…1: 0或1个实例.
  • 0…: 0或多个实例.
  • 1…1: 1个实例.
  • 1只能有一个实例.
  • 1…: 至少有一个实例.

IDEA插件是实心菱形加虚线箭头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class School {
private List<Student> students ;

public List<Student> getStudents() {
return students;
}

public void setStudents(List<Student> students) {
this.students = students;
}
}

public class Student {
}

聚合:代表空器皿里可以放很多相同东西,聚在一起(箭头方向所指的类)

  • 整体和局部的关系,两者有着独立的生命周期,是has a的关系
  • 弱关系

组合

实心菱形

更强调整体与部分,整体与部分是同生共死的

IDEA插件是实心菱形加虚线箭头加create

1
2
3
4
5
6
7
8
9
10
public class Computer {
private Cpu cpu = new Cpu();
private Gpu gpu = new Gpu();
}

public class Cpu {
}

public class Gpu {
}
  • 整体与局部的关系,和聚合的关系相比,关系更加强烈两者有相同的生命周期,contains-a的关系
  • 强关系

访问修饰符

访问修饰符符号
public+
protected#
default~
private-
抽象属性下划线标注
抽象方法斜体

idea插件表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public abstract class User {
public int id;
protected String name;
int age;
private String address;
public static String sex;

public int getId() {
return id;
}

protected String getName() {
return name;
}

int getAge() {
return age;
}

private String getAddress() {
return address;
}

public static String getSex() {
return sex;
}

public abstract String abstractFun(int a);
}

软件设计七大原则

参考文章:https://cloud.tencent.com/developer/article/1650116

参考文章:https://juejin.cn/post/7027666637964705806

参考文章:https://juejin.cn/post/6844903795017646094#heading-20

设计模式

参考文章:https://pdai.tech/md/dev-spec/pattern/3_simple_factory.html

参考文章:https://refactoringguru.cn

创建型

设计模式定义适用场景
简单工厂根据不同的参数创建不同的产品工厂类负责创建的对象较少
工厂方法定义一个创建对象的接口,允许子类决定
实例化对象的类型
创建对象需要大量重复的代码(产品等级)
抽象工厂抽象工厂模式提供一个创建一系列相关或
相互依赖对象的接口
强调一系列相关的产品对象(属于同一产品族)
一起使用创建对象需要大量重复的代码
建造者将一个复杂对象的构建与它的表示分离,
使得同样的构建过程可以创建不同的表示
一个对象有非常复杂的内部结构
原型指原型实例指定创建对象的种类,并且通
过拷贝这些原型创建新的对象
类初始化消耗较多资源;循环体中生产大量对象时
单例保证一个类只有一个实例程序中的某个类对于所有客户端只有一个可用的实
例,可以使用单例模式

结构型

设计模式定义适用场景
外观(门面)提供了一个统一的接口,用来访问
子系统中的一群接口
构建多层系统结构,利用外观对象作为每
层的入口,简化层间调用
装饰者不改变原有对象的基础之上,将功
能附加到对象上
扩展一个类的功能或给一个类添加附加职责
适配器将一个类的接口转换成客户期望的
另一个接口
已经存在的类,它的方法和需求不匹配时
享元通过共享多个对象所共有的相同状态,
在有限的内存容量中载入更多对象
系统有大量相似对象、需要缓冲池的场景
组合使客户端对单个对象和组合对象保持一
致的方式处理
需要实现树状对象结构, 可以使用组合模式
桥接可将一个大类或一系列紧密相关的类拆分
为抽象和实现两个独立的层次结构, 从而
能在开发时形成多维度的扩展体系
希望在几个独立维度上扩展一
个类, 可使用该模式
代理为其他对象提供一种代理,以控制对这个
对象的访问
保护目标对象;增强目标对象

行为型

设计模式定义适用场景
模板方法它在超类中定义了一个算法的框架, 允许
子类在不修改结构的情况下重写算法的特定
步骤
希望客户端扩展某个特定算法步骤,
而不是整个算法或其结构时, 可使
用模板方法模式
迭代器提供一种方法,顺序访问一个集合对象中的
各个元素,而又不暴露该对象的内部表示
希望代码能够遍历不同的甚至是无法预
知的数据结构, 可以使用迭代器模式
策略它能让你定义一系列算法, 并将每种算法分
别放入独立的类中, 以使算法的对象能够相互替换
当你想使用对象中各种不同的算法变体
, 并希望能在运行时切换算法时, 可
使用策略模式
解释器为了解释一种语言,而为语言创建的解释器(低频)某个特定类型问题发生频率足
够高
观察者定义一种订阅机制, 可在对象事件发生时
通知多个 “观察” 该对象的其他对象
关联行为场景,建立一套触发机制
备忘录保存一个对象的某个状态,以便在适当的
时候恢复对象
保存及恢复数据相关业务场景(binlog)
命令将请求转换为一个包含与请求相关的所有信
息的独立对象,该转换让你能根据不同的请
求将方法参数化、 延迟请求执行或将其放入队
列中, 且能实现可撤销操作
想要将操作放入队列中、 操作的执行
或者远程执行操作, 可使用命令模式
中介者减少对象之间混乱无序的依赖关系。 该模式
会限制对象之间的直接交互, 迫使它们通过
一个中介者对象进行合作
当一些对象和其他对象紧密耦合以致
难以对其进行修改时, 可使用中介者
模式
责任链允许你将请求沿着处理者链进行发送。 收
到请求后, 每个处理者均可对请求进行处理, 或将
其传递给链上的下个处理者
当程序需要使用不同方式处理不同种
类请求, 而且请求类型和顺序预先未
知时, 可以使用责任链模式
访问者它能将算法与其所作用的对象隔离开来数据结构与数据操作分离
状态允许一个对象在其内部状态改变时,改
变它的行为
如果对象需要根据自身当前状态进行不
同行为, 同时状态的数量非常多且与状
态相关的代码会频繁变更的话, 可使
用状态模式

创建型

简单工厂

根据不同的参数创建不同的产品

适用场景

  • 工厂类负责创建的对象比较少

  • 客户端(应用层)只知道传入工厂类的参数对于如何创建对象(逻辑)不关心

优缺点

优点:只需要传入一个正确的参数,就可以获取你所需要的对象而无须知道其创建细节

缺点:工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,违背开闭原则

角色说明

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能
  • 具体产品:实现或继承了抽象产品的类
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来创建产品

模拟场景

咖啡店出售美式咖啡和拿铁咖啡,模拟下单场景

1
2
3
4
5
6
/**
* 抽象产品
**/
public interface Coffee {
void drink();
}
1
2
3
4
5
6
7
8
9
/**
* 具体产品
**/
public class AmericanCoffee implements Coffee{
@Override
public void drink() {
System.out.println("喝美式咖啡");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 具体工厂
**/
public class CoffeeShop {
public static Coffee create(String type) {
Coffee coffee = null;
switch (type) {
case "american":
coffee = new AmericanCoffee();
break;
case "latte":
coffee = new LatteCoffee();
break;
}
return coffee;
}
}
1
2
3
4
5
6
7
8
9
public class Example {
public static void main(String[] args) {
Coffee american = CoffeeShop.create("american");
american.drink();

Coffee latte = CoffeeShop.create("latte");
latte.drink();
}
}

源码解析

Spring框架的BeanFactory就使用简单工厂模式提供Bean的获取

DefaultListableBeanFactory#getBean(Class)源码,用户不必关心Bean的创建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
return getBean(requiredType, (Object[]) null);
}

@SuppressWarnings("unchecked")
@Override
public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
Assert.notNull(requiredType, "Required type must not be null");
Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);
if (resolved == null) {
throw new NoSuchBeanDefinitionException(requiredType);
}
return (T) resolved;
}

工厂方法

针对产品等级结构 不同的工厂生产不同的产品

定义

定义一个创建对象的接口,允许子类决定实例化对象的类型


适用场景

  • 创建对象需要大量重复的代码
  • 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
  • 一个类通过其子类来指定创建哪个对象

优缺点

优点

  • 用户只需要关心所需产品对应的工厂,无须关心创建细节
  • 加入新产品符合开闭原则,提高可扩展性

缺点

  • 类的个数容易过多,增加复杂度
  • 增加了系统的抽象性和理解难度

角色说明

抽象产品:将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的

具体产品:是产品接口的不同实现

工厂方法:定义返回产品对象的工厂方法

具体创建者:将会重写基础工厂方法, 使其返回不同类型的产品


模拟场景

夏天,冰箱供销商需要买进一系列冰箱,冰箱有美的,格力的品牌,不同的品牌又有各自的工厂

1
2
3
4
5
6
/**
* 冰箱(抽象产品)
**/
public abstract class Fridge {
abstract String getBrand();
}
1
2
3
4
5
6
7
8
9
/**
* 格力冰箱(具体产品)
**/
public class GreeFridge extends Fridge{
@Override
String getBrand() {
return "格力冰箱";
}
}
1
2
3
4
5
6
7
8
9
/**
* 美的冰箱(具体产品)
**/
public class MideaFirdge extends Fridge{
@Override
String getBrand() {
return "美的冰箱";
}
}
1
2
3
4
5
6
/**
* 工厂方法
**/
public abstract class Factory {
public abstract Fridge create();
}
1
2
3
4
5
6
7
8
9
/**
* 格力工厂(具体创建者)
**/
public class GreeFactory extends Factory {
@Override
public Fridge create() {
return new GreeFridge();
}
}
1
2
3
4
5
6
7
8
9
/**
* 美的工厂(具体创建者)
**/
public class MideaFactory extends Factory{
@Override
public Fridge create() {
return new MideaFirdge();
}
}
1
2
3
4
5
6
7
8
9
public class Example {
public static void main(String[] args) {
Factory greeFactory = new GreeFactory();
Factory mideaFactory = new MideaFactory();

Fridge greeFridge = greeFactory.create();
Fridge mideaFridge = mideaFactory.create();
}
}

源码解析

Spring框架的FactoryBean体系就用到了方法工厂模式,每个FactoryBean用户都可以自定义Bena的创建过程

抽象工厂

针对产品族 不同工厂生产不同品族的产品

定义

  • 抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口
  • 无须指定它们具体的类

适用场景

  • 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
  • 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码
  • 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现

优缺点

优点

  • 具体产品在应用层代码隔离,无须关心创建细节
  • 将一个系列的产品族统一到一起创建

缺点

  • 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口
  • 增加了系统的抽象性和理解难度

产品等级结构和产品族

上图表示产品族产品等级结构的关系

  • 产品族:具有同一个地区、同一个厂商、同一个开发包、同一个组织模块等,但是具备不同特点或功能的产品集合,称之为是一个产品族

  • 产品等级结构:具有相同特点或功能,但是来自不同的地区、不同的厂商、不同的开发包、不同的组织模块等的产品集合,称之为是一个产品等级结构

当程序中的对象可以被划分为产品族产品等级结构之后,那么抽象工厂方法模式才可以被适用


角色说明

抽象产品: 为构成系列产品的一组不同但相关的产品声明接口

具体产品:抽象产品的多种不同类型实现

抽象工厂:接口声明了一组创建各种抽象产品的方法

具体工厂:现抽象工厂的构建方法。 每个具体工厂都对应特定产品变体, 且仅创建此种产品变体


模拟场景

教育网有java、python课程,教程又分为视频和文章两部分

1
2
3
4
5
6
/**
* 文章(抽象产品)
**/
public interface Article {
void produce();
}
1
2
3
4
5
6
7
8
9
/**
* java 文章(具体产品)
**/
public class JavaArticle implements Article {
@Override
public void produce() {
System.out.println("编写java文章");
}
}
1
2
3
4
5
6
7
8
9
/**
* python 文章(具体产品)
**/
public class PythonArticle implements Article{
@Override
public void produce() {
System.out.println("编写Python文章");
}
}
1
2
3
4
5
6
/**
* 视频(抽象产品)
**/
public interface Video {
void produce();
}
1
2
3
4
5
6
7
8
9
/**
* java 视频(具体产品)
**/
public class JavaVideo implements Video{
@Override
public void produce() {
System.out.println("录制Java视频");
}
}
1
2
3
4
5
6
7
8
9
/**
* python 视频(具体产品)
**/
public class PythonVideo implements Video{
@Override
public void produce() {
System.out.println("录制Python视频");
}
}
1
2
3
4
5
6
7
/**
* 课程抽象工厂(抽象工厂,声明一组创建抽象产品的方法)
**/
public interface CourseFactory {
Video createVideo();
Article createArticle();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* java工厂,创建java课程产品族
**/
public class JavaCourseFactory implements CourseFactory{
@Override
public Video createVideo() {
return new JavaVideo();
}

@Override
public Article createArticle() {
return new JavaArticle();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Python工厂,创建Python课程产品族
**/
public class PythonCourseFactory implements CourseFactory{
@Override
public Video createVideo() {
return new PythonVideo();
}

@Override
public Article createArticle() {
return new PythonArticle();
}
}
1
2
3
4
5
6
7
public class Example {
public static void main(String[] args) {
CourseFactory courseFactory = new JavaCourseFactory();
Video javaVideo = courseFactory.createVideo();
Article javaArticle = courseFactory.createArticle();
}
}

源码解析

Mybatis

Spring

建造者

建造者模式更注重创建产品的步骤,工厂模式注重创建成品

定义

  • 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
  • 用户只需指定需要建造的类型就可以得到它们,建造过程及细节不需要知道

适用场景

  • 如果一个对象有非常复杂的内部结构(很多属性)
  • 想把复杂对象的创建和使用分离

优缺点

优点

  • 封装性好,创建和使用分离
  • 扩展性好、建造类之间独立、一定程度上解耦

缺点

  • 产生多余的Builder对象
  • 产品内部发生变化,建造者都要修改,成本较大

角色说明

产品:是最终生成的对象。 由不同生成器构造的产品无需属于同一类层次结构或接口

生成器:接口声明,在所有类型生成器中通用的产品构造步骤

具体生成器:提供构造过程的不同实现。 具体生成器也可以构造不遵循通用接口的产品

主管(Director):定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置


使用

1
2
3
4
5
6
7
8
9
10
/**
* 产品
**/
@Data
@ToString
public class Product {
private Integer id;
private BigDecimal price;
private Integer stock;
}
1
2
3
4
5
6
7
8
9
10
/**
* 生成器
**/
public abstract class Builder {
abstract void buildProductId(Integer id);
abstract void buildProductPrice(BigDecimal price);
abstract void buildProductStock(Integer stock);

abstract Product makeProduct();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 具体生成器
**/
public class ProductBuilder extends Builder{
private Product product = new Product();

@Override
void buildProductId(Integer id) {
product.setId(id);
}

@Override
void buildProductPrice(BigDecimal price) {
product.setPrice(price);
}

@Override
void buildProductStock(Integer stock) {
product.setStock(stock);
}

@Override
Product makeProduct() {
return product;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 主管类,定义调用构造步骤的顺序
**/
public class Director {
private Builder builder;

public Director(Builder builder) {
this.builder = builder;
}

public Product makeProduct(Integer id, BigDecimal price, Integer stock) {
this.builder.buildProductId(id);
this.builder.buildProductPrice(price);
this.builder.buildProductStock(stock);
return builder.makeProduct();
}
}
1
2
3
4
5
6
7
8
public class Example {
public static void main(String[] args) {
Builder builder = new ProductBuilder();
Director director = new Director(builder);
Product product = director.makeProduct(1, new BigDecimal(10), 10);
System.out.println(product);
}
}

内部静态类模式(常用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Data
@ToString
public class Product {
public Product(Integer id, BigDecimal price, Integer stock) {
this.id = id;
this.price = price;
this.stock = stock;
}

private Integer id;
private BigDecimal price;
private Integer stock;


public static ProductBuilder builder() {
return new ProductBuilder();
}

// 构建类
public static class ProductBuilder {
private Integer id;
private BigDecimal price;
private Integer stock;

// 链式调用
public Product.ProductBuilder id(final Integer id) {
this.id = id;
return this;
}

public Product.ProductBuilder price(final BigDecimal price) {
this.price = price;
return this;
}

public Product.ProductBuilder stock(final Integer stock) {
this.stock = stock;
return this;
}

// 通过构造函数创建对象
public Product build() {
return new Product(this.id, this.price, this.stock);
}
}
}
1
2
3
4
5
6
7
8
9
10
public class Example {
public static void main(String[] args) {
Product product = Product.builder()
.id(1)
.price(new BigDecimal(10))
.stock(10)
.build();
System.out.println(product);
}
}

源码解析

JDK

  • StringBuilder
  • StringBuffer

底层都是byte[],使用构建者模式简化字符串拼接

Mybatis

  • SqlSessionFactoryBuilder:使用mybatis.xml配置文件通过SqlSessionFactoryBuilder 建造 SqlSessionFactory
  • XMLConfigBuilder
  • XMLMapperBuilder
  • XMLStatementBuilder
  • CacheBuilder


Spring

BeanDefinitionBuilder用于构造BeanDefinition

原型

细胞分裂 依赖对象的克隆复制新的对象

定义

  • 指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
  • 不需要知道任何创建的细节,不调用构造函数

适用场景

  • 类初始化消耗较多资源
  • new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
  • 构造函数比较复杂
  • 循环体中生产大量对象时

优缺点

优点

  • 原型模式性能比直接new一个对象性能高
  • 简化创建过程

缺点

  • 必须配备克隆方法
  • 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险
  • 深拷贝、浅拷贝要运用得当

深克隆、浅克隆

参考 深拷贝和浅拷贝

深拷贝还可以通过序列化的方式来实现


角色说明

原型:接口将对克隆方法进行声明,JDK提供的Cloneable接口是个不错的选择

具体原型:实现克隆方法。 除了将原始对象的数据复制到克隆体中之外, 该方法有时还需处理克隆过程中的极端情况, 例如克隆关联对象和梳理递归依赖等等

客户端:可以复制实现了原型接口的任何对象


模拟场景

批量发送营销邮件,邮件除了邮件地址其他内容都一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 具体原型
**/
@Data
@ToString
public class Mail implements Cloneable{
private String emailAddress;
private String content;

// 实现克隆方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Example {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建原型
Mail prototype = new Mail();
prototype.setContent("双十一大促销!");

for (int i = 0; i < 10; i++) {
// 根据原型克隆
Mail mail = (Mail) prototype.clone();
mail.setEmailAddress(i + "@test.com");
send(mail);
}
}

public static void send(Mail mail) {
System.out.println(mail);
}
}

单例

系统中只存在一个实例

定义

能够保证一个类只有一个实例, 并提供一个访问该实例的全局入口


优缺点

优点

  • 单例模式可以保证内存里只有一个实例,减少了内存的开销
  • 为共享资源提供一个全局统一的访问点

缺点

  • 单例模式一般没有实现接口,扩展困难
  • 单例模式如果业务过重,则很容易违背单一责职原则

适用场景

  • 如果程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式

  • 如果你需要更加严格地控制全局变量,可以使用单例模式

  • 数据库连接池的设计一般采用单例模式

容器单例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ContainerSingleton {

// 防止实例化
private ContainerSingleton() {

}

// 单例容器
private static Map<String, Object> singletonMap = new ConcurrentHashMap<>();


/**
* 将实例放入容器
*
* @param key
* @param instance
*/
public static void setInstance(String key, Object instance) {
if (Objects.isNull(key)) {
throw new IllegalArgumentException("key is null");
}

if (Objects.isNull(instance)) {
throw new IllegalArgumentException("instance is null");
}

singletonMap.computeIfAbsent(key, k -> instance);
}

public static Object getInstance(String key) {
if (Objects.isNull(key)) {
throw new IllegalArgumentException("key is null");
}

return singletonMap.get(key);
}
}
1
2
3
4
5
6
public class Example {
public static void main(String[] args) {
ContainerSingleton.setInstance("one", 1);
ContainerSingleton.setInstance("two", 2);
}
}

容器单例模式不负责对象的创建过程,而是将已有对象存入容器重复使用,让容器保证实例的单例性,常用于缓存

懒汉式

非线程安全 懒加载思想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static Singleton singleton;

// 避免被实例化
private Singleton() {
}

public static Singleton getSingleton() {
// 比较懒,用到才判断
if (singleton == null) {
// 并发情况下有线程安全问题
singleton = new Singleton();
}
return singleton;
}
}

懒汉式最大的缺点就是面对多线程获取单例的时候,无法保证线程安全,可能会创建多个实例

饿汉式

线程安全

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
// 避免被实例化
private Singleton() {
}

// 非常饥饿,一上来就实例化
private static final Singleton instance = new Singleton();

public static Singleton getInstance() {
return instance;
}
}

饿汉式是线程安全的,依赖于JVM类加载时机创建对象。缺点在于无法实现懒加载,造成资源浪费

双重检查

volative修饰保证线程可见性 synchronized双重检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton {
// 避免被实例化
private Singleton() {
}

// 保证多线程可见性
private static volatile Singleton instance;

public Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
// 存在并发线程安全
if (instance == null) {
instance = new Singleton();
}
}
}

return instance;
}
}

双重检查解决并发获取实例时线程安全问题

枚举(推荐)

破坏单例模式

  • 内部枚举方式可以防止使用反射和序列化破坏单例模式
  • 依赖JVM对枚举实例创建的线程安全性创建单例
1
2
3
4
5
6
7
public enum Singleton {
INSTANCE;

public void doSomething() {
System.out.println("doSomething");
}
}
1
2
3
4
5
public class Example {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}

结构型

外观(门面)

微服务就是使用外观模式,向外提供简单的Api调用

定义

  • 又叫门面模式,提供了一个统一的接口,用来访问子系统中的一群接口
  • 外观模式定义了一个高层接口,让子系统更容易使用

适用场景

  • 子系统越来越复杂,增加外观模式提供简单调用接口
  • 构建多层系统结构,利用外观对象作为每层的入口,简化层间调用

优缺点

优点

  • 简化了调用过程,无需了解深入子系统,防止带来风险
  • 减少系统依赖、松散耦合
  • 更好的划分访问层次
  • 符合迪米特法则,即最少知道原则

缺点

  • 增加子系统、扩展子系统行为容易引入风险
  • 不符合开闭原则

与其他设计模式对比

外观模式和中介者

  • 外观模式关注的是外界和子系统的交互
  • 中介者模式关注的是子系统内部的交互

外观模式和单例模式

  • 通常把外观模式的外观对象做成单例模式来使用

外观模式和抽象工厂模式

  • 外观类可以通过抽象工厂获取子系统实例,子系统可以将内部对外观类屏蔽

模拟场景

分布式系统中的下单场景,系统分库存服务,订单服务,物流服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 订单外观类,调用分布式分服务
**/
public class OrderFacade {
// 正常情况是注入,都是远程RPC服务
private StockService stockService = new StockService();
private OrderService orderService = new OrderService();
private LogisticsService logisticsService = new LogisticsService();

// 下单, 对外屏蔽子系统
public void create(OrderParam orderParam) {
// 库存扣减
stockService.deduction(orderParam);
// 生成订单
orderService.create(orderParam);
// 生成物流单
logisticsService.create(orderParam);
}
}
1
2
3
4
5
6
7
8
/**
* 库存服务
**/
public class StockService {
public void deduction(OrderParam param) {
System.out.println("库存扣减");
}
}
1
2
3
4
5
6
7
8
/**
* 订单服务
**/
public class OrderService {
public void create(OrderParam param) {
System.out.println("生成订单");
}
}
1
2
3
4
5
6
7
8
/**
* 物流服务
**/
public class LogisticsService {
public void create(OrderParam param) {
System.out.println("生成物流单号");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Client {

public static void main(String[] args) {
OrderParam param = new OrderParam();
param.setNum(1);
param.setSku("test");

// 外观类
OrderFacade facade = new OrderFacade();
facade.create(param);
}
}

源码解析

Mybatis

Mybatis通过外观设计模式封装了SqlSession会话层,对外屏蔽了Executor调用的复杂性


Spring JDBC

Spring JDBC的JdbcUtils通过对原生的 jdbc 进行了封装降低原生jdbc使用的复杂性

装饰者

不改变原有对象扩展对象功能

定义

  • 在不改变原有对象的基础之上,将功能附加到对象上
  • 提供了比继承更有弹性的替代方案(扩展原有对象功能)

适用场景

  • 扩展一个类的功能或给一个类添加附加职责
  • 动态的给一个对象添加功能,这些功能可以再动态的撤销

优缺点

优点

  • 继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能
  • 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果
  • 符合开闭原则

缺点

  • 会出现更多的代码,更多的类,增加程序复杂性
  • 动态装饰时,多层装饰时会更复杂

角色说明

  • 抽象构件:它是具体构件和抽象装饰类的共同父类,声明了要在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象。抽象构件一般定义为接口
  • 具体构件:它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰对象可以给它增加额外的职责(方法)
  • 抽象装饰类:它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的
  • 具体装饰类:它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为

与其他设计模式对比

装饰者模式和代理模式

相同点

  • 两种从设计模式分类来看都属于结构型,因为两者均使用了组合关系。其次两者都能实现对对象方法进行增强处理的效果

不同点

  • 装饰者模式:注重的是对对象提供增强功能
  • 代理模式:对所代理对象的使用施加控制,并不提供对象本身的增强功能

装饰者模式和适配器模式

相同点

  • 装饰器与适配器都有一个别名叫做 包装模式(Wrapper),它们看似都是起到包装一个类或对象的作用

不同点

  • 适配器模式:是要将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的
  • 装饰器模式:不是要改变被装饰对象的接口,而是恰恰要保持原有的接口,但是增强原有对象的功能

模拟场景

奶茶店出售的奶茶可以是基本的珍珠奶茶,用户也可以选择自定义添加底料,比如加奶盖,焦糖

1
2
3
4
5
6
7
/**
* 定义奶茶(抽象构件)
**/
public abstract class AbstractMilkTea {
protected abstract String getDesc();
protected abstract int getPrice();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 奶茶(具体构件)
**/
public class MilkTea extends AbstractMilkTea {
@Override
protected String getDesc() {
return "珍珠奶茶";
}

@Override
protected int getPrice() {
return 8;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 抽象装饰类继承抽象构件
**/
public abstract class AbstractDecorator extends AbstractMilkTea {
// 抽象装饰类持有构件
protected AbstractMilkTea milkTea;

public AbstractDecorator(AbstractMilkTea milkTea) {
this.milkTea = milkTea;
}

// 装饰类扩展的方法,是否赠送礼品
protected abstract boolean sendGifts();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/** 
* 加焦糖
**/
public class CaramelDecorator extends AbstractDecorator {
public CaramelDecorator(AbstractMilkTea milkTea) {
super(milkTea);
}

@Override
protected boolean sendGifts() {
return false;
}

// 对原有对象进行增强
@Override
protected String getDesc() {
return milkTea.getDesc() + "加焦糖";
}

@Override
protected int getPrice() {
return milkTea.getPrice() + 2;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** 
* 加奶盖
**/
public class MilkCapDecorator extends AbstractDecorator {
public MilkCapDecorator(AbstractMilkTea milkTea) {
super(milkTea);
}

@Override
protected boolean sendGifts() {
return true;
}

@Override
protected String getDesc() {
return milkTea.getDesc() + "加奶盖";
}

@Override
protected int getPrice() {
return milkTea.getPrice() + 5;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Example {

public static void main(String[] args) {
// 珍珠奶茶
AbstractMilkTea abstractMilkTea = new MilkTea();

// 加焦糖
abstractMilkTea = new CaramelDecorator(abstractMilkTea);
System.out.println(abstractMilkTea.getDesc());
System.out.println(abstractMilkTea.getPrice());

// 加奶盖
abstractMilkTea = new MilkCapDecorator(abstractMilkTea);
System.out.println(abstractMilkTea.getDesc());
System.out.println(abstractMilkTea.getPrice());
}
}

源码解析

jdk的缓冲流体系

*Buffered* 的缓冲流都是对输入输出流进行包装达增强,达到快速读写目的

Spring

Spring 在Cache体系中使用了装饰者模式对缓存进行事务感知。简单的、事务感知的缓存装饰器,它在提交之前暂时保存缓存值,以避免在回滚时用无效值污染缓存

适配器

电源适配器,通过电压转换后各种设备可以正常使用

定义

  • 将一个类的接口转换成客户期望的另一个接口
  • 使原本接口不兼容的类可以一起工作

适用场景

  • 已经存在的类,它的方法和需求不匹配时(方法结果相同或相似,对不匹配的类进行复用)
  • 不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案

优缺点

优点

  • 能提高类的透明性和复用,现有的类复用但不需要改变
  • 目标类和适配器类解耦,提高程序扩展性

缺点

  • 适配器编写过程需要全面考虑,可能会增加系统的复杂性
  • 增加系统代码可读的难度

与其他设计模式对比

适配器和外观模式

  • 相同点:都是对现有的类进行封装

  • 外观模式:定义了新的统一接口封装了子系统的一群接口

  • 适配器模式:主要是复用一个原有的接口,使已有的两个接口适配工作


模拟场景 (对象适配)

中国的电压是220V的,美国的电压是110V的,使用一个电源适配器让美国的家电能在中国使用

1
2
3
4
5
6
7
/**
* 中国电压标准
*/
public interface ChinaVoltageStandard {
// 定义输出220V
int output220V();
}
1
2
3
4
5
6
7
8
9
10
/**
* 中国电压
*/
public class ChinaVoltage implements ChinaVoltageStandard {
@Override
public int output220V() {
System.out.println("中国电压输出220V");
return 220;
}
}
1
2
3
4
5
6
7
8
/**
* 美国电压标准
*/
public interface UsVoltageStandard {

// 定义输出110V
int output110V();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 美国电压适配器遵循美国电压标准
* 将220V的电转为110V的电
*/
public class UsVoltageAdapter implements UsVoltageStandard {
private ChinaVoltage chinaVoltage = new ChinaVoltage();

@Override
public int output110V() {
System.out.println("电压适配器转换电压");
int voltage = chinaVoltage.output220V() / 2;
return voltage;
}
}
1
2
3
4
5
6
public class Example {
public static void main(String[] args) {
UsVoltageStandard usVoltage = new UsVoltageAdapter();
System.out.println("输出电压:" + usVoltage.output110V());
}
}

类适配

适配器继承被适配类实和现适配接口

场景:服务重构,老订单服务适配新订单服务接口

1
2
3
4
public interface OrderService {
// 下单支付
boolean pay(String orderNo, Integer total);
}
1
2
3
4
5
6
7
public class OrderServiceImpl implements OrderService {
@Override
public boolean pay(String orderNo, Integer total) {
System.out.println("OrderService 下单扣款金额" + total);
return true;
}
}
1
2
3
public interface OrderServiceV2 {
boolean pay(String orderNo, Integer total, Integer discount);
}
1
2
3
4
5
6
7
8
public class OrderServiceV2Impl implements OrderServiceV2{
@Override
public boolean pay(String orderNo, Integer total, Integer discount) {
total = total - discount;
System.out.println("OrderServiceV2 下单扣款金额" + total);
return true;
}
}
1
2
3
4
5
6
7
8
public class OrderServiceV2Adapter extends OrderServiceImpl implements OrderServiceV2 {
@Override
public boolean pay(String orderNo, Integer total, Integer discount) {
// 适配逻辑
total = total - discount;
return super.pay(orderNo, total);
}
}
1
2
3
4
5
6
public class Example {
public static void main(String[] args) {
OrderServiceV2 orderService = new OrderServiceV2Adapter();
orderService.pay("t123", 10, 2);
}
}

源码解析

Spring AOPAdvisor适配为MethodInterceptor


Spring MVC的HandlerAdapter,spring定义了一个适配器接口,使得每一种Controller有一种对应的适配器实现类。Controller种类很多,有需要视图渲染的,参数解析、封装的,有直接返回Json、Xml数据的。

享元

共享 常用于缓存框架 减少对象的创建,减少内存的占用

定义

  • 提供了减少对象数量从而改善应用所需的对象结构的方式
  • 通过共享多个对象所共有的相同状态, 在有限的内存容量中载入更多对象

适用场景

字符串常量池 数据库连接池

  • 常常应用于系统底层的开发,以便解决系统的性能问题
  • 系统有大量相似对象、需要缓冲池的场景

优缺点

优点

  • 减少对象的创建,降低内存中对象的数量,降低系统的内存,提高效率
  • 减少内存之外的其他资源占用(比如new对象占用的创建时间,文件句柄,窗口句柄)

缺点

  • 关注内/外部状态、关注线程安全问题
  • 使系统、程序的逻辑复杂化

扩展

  • 内部状态:内部状态是存储在享元对象内部并且不会随环境变化而变化的状态,因此内部状态可以共享
  • 外部状态:外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外部状态之间是相互独立的

与其他设计模式对比

享元模式和代理模式

  • 代理模式需要代理一个类,如果生成这个代理类需要花费的时间和资源非常多,则可以使用享元模式提高程序的处理速度

享元模式和单例模式

  • 根据不同的业务场景使用,比如缓存级别的对象复用,则使用享元模式。如果是工具级别的对象并且其创建过程很耗费资源则使用单例模式

模拟场景

享元工厂

在一个数据库会话中实现一个数据库查询缓存插件,将查询结构存入缓存,不需要每次都去数据库中查询

1
2
3
4
5
6
7
8
/**
* 缓存接口
*/
public interface Cache {

// 获取缓存数据
Object getData();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 缓存实现
*/
public class SimpleCache implements Cache {

public SimpleCache(Object data) {
this.data = data;
}

//缓存时间,内部状态,不会随着外部环境改变,可用于控制缓存失效
private Date cacheTime = new Date();

// 外部状态
private Object data;

@Override
public Object getData() {
return data;
}

@Override
public String toString() {
return "SimpleCache{" +
"cacheTime=" + cacheTime +
", data=" + data +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 缓存工厂
*/
public class CacheFactory {
private final static Map<String, Cache> CACHE_CONTAINER = new ConcurrentHashMap<>();

public static Cache getCache(String sql) {
// 模拟缓存没有则从数据库中查询
Cache cache = CACHE_CONTAINER.computeIfAbsent(sql, (key) -> new SimpleCache((int)(Math.random() * 100)));
return cache;
}
}
1
2
3
4
5
6
7
8
9
10
public class Example {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
int no = (int)(Math.random() * 2);
String sql = "select id from user where no=" + no;
Cache cache = CacheFactory.getCache(sql);
System.out.println(sql + " -> " + cache);
}
}
}

源码解析

JDK

Java源码中的Integer存在缓存池,如果取值在[-128,127],则取缓存池中的对象

Mybatis

Mybatis的Configuration类缓存了MappedStatementResultMapParameterMap等信息

组合

目录 菜单 将多种类型的对象按照一致的方式处理

定义

  • 将对象组合成树形结构以表示部分-整体的层次结构
  • 组合模式使客户端对单个对象和组合对象保持一致的方式处理

适用场景

  • 希望客户端可以忽略组合对象与单个对象的差异时
  • 如果你需要实现树状对象结构, 可以使用组合模式

优缺点

优点

  • 清楚地定义分层次的复杂对象,表示对象的全部或部分层次
  • 让客户端忽略了层次的差异,方便对整个层次结构进行控制
  • 简化客户端代码

缺点

  • 限制类型时会较为复杂(多个不同的对象不一定都满足基类的方法)
  • 使设计变得更加抽象

角色说明

  • 组件接口:描述了树中简单项目和复杂项目所共有的操作
  • 叶节点:是树的基本结构, 它不包含子项目
  • 容器:又名 组合 (Composite)是包含叶节点或其他容器等子项目的单位。 容器不知道其子项目所属的具体类, 它只通过通用的组件接口与其子项目交互

与其他设计模式对比

组合模式和访问者模式

可以使用访问者模式来访问组合模式的递归结构


模拟场景

模拟系统菜单,菜单分为菜单目录和菜单项,菜单目录有名称并且能够添加菜单项,菜单项不能继续添加菜单项但有菜单名称和跳转Url,构建一个目录树,打印出所有的菜单目录和菜单项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 菜单类(组件)
*/
public abstract class Menu {
public String getName() {
throw new UnsupportedOperationException("不支持获取名称");
}

public String getUrl() {
throw new UnsupportedOperationException("不支持获取url");
}

public int getLevel() {
throw new UnsupportedOperationException("不支持获取level");
}

public void add(Menu menu) {
throw new UnsupportedOperationException("不支持添加操作");
}

public void remove(Menu menu) {
throw new UnsupportedOperationException("不支持删除操作");
}

public void print() {
throw new UnsupportedOperationException("不支持删除操作");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 菜单目录(容器)
*/
public class MenuCatalog extends Menu {
// 目录下的菜单项
private List<Menu> items = new ArrayList<>();
private String name;
private int level;

public MenuCatalog(String name, int level) {
this.name = name;
this.level = level;
}

@Override
public String getName() {
return this.name;
}

@Override
public int getLevel() {
return level;
}

@Override
public void add(Menu menu) {
this.items.add(menu);
}

@Override
public void remove(Menu menu) {
this.items.remove(menu);
}

@Override
public void print() {
System.out.println(this.name);

for (Menu item : items) {
// 类型限制,组合模式的坑点,打印缩进
for (int i = 0; i < item.getLevel(); i++) {
System.out.print(" ");
}
item.print();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 菜单项(叶节点)
*/
public class MenuItem extends Menu {
private String name;
private String url;
private int level;

public MenuItem(String name, String url, int level) {
this.name = name;
this.url = url;
this.level = level;
}

@Override
public String getUrl() {
return url;
}

@Override
public int getLevel() {
return level;
}

@Override
public String getName() {
return name;
}

@Override
public void print() {
System.out.println("name:" + name + " url:" + url);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Example {
public static void main(String[] args) {
Menu root = new MenuCatalog("主目录", 1);
Menu search = new MenuCatalog("搜索类", 2);
Menu video = new MenuCatalog("视频类", 2);
root.add(search);
root.add(video);

Menu google = new MenuItem("谷歌", "https://google.com", 3);
Menu baidu = new MenuItem("百度", "https://www.baidu.com", 3);
search.add(google);
search.add(baidu);

Menu bilibili = new MenuItem("B站", "https://www.bilibili.com", 3);
Menu youku = new MenuItem("B站", "https://youku.com", 3);
video.add(bilibili);
video.add(youku);

root.print();
}
}

// 输出
主目录
搜索类
name:谷歌 url:https://google.com
name:百度 url:https://www.baidu.com
视频类
name:B站 url:https://www.bilibili.com
name:B站 url:https://youku.com

源码解析

JDK

JDK源码的ArrayList#addAll方法的入参类型不是List,而是它的父类Collection

Spring

Spring缓存模块的CompositeCacheManager复合缓存管理器可以管理不同中间件的缓存。如CaffeineCacheManager,RedisCacheManager

桥接

抽象和实现分离 避免子类爆炸 通过组合连接两个继承体系

定义

  • 将抽象部分与它的具体实现部分分离,使它们都可以独立地变化
  • 通过组合的方式建立两个类之间联系,而不是继承

适用场景

  • 如果希望在几个独立维度上扩展一个类, 可使用该模式
  • 如果需要在运行时切换不同实现方法, 可使用桥接模式(使用聚合实现)
  • 不希望使用继承,或因为多层继承导致系统类的个数剧增

优缺点

优点

  • 分离抽象部分及其具体实现部分
  • 提高了系统的可扩展性
  • 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息(桥接是两个继承体系抽象层的聚合)

缺点

  • 增加了系统的理解与设计难度
  • 需要正确地识别出系统中两个独立变化的维度

与其他设计模式对比

桥接模式和组合模式

  • 组合模式:强调的是部分和整体的组合
  • 桥接模式:强调平行级别上不同类的组合

桥接模式和适配器模式

  • 共同点:都是让两个不同的对象配合工作
  • 不同点
    • 适配器模式:改变已有的接口,让它们之间可以相互配合,让功能上相似但接口不同的类适配起来
    • 桥接模式:分离抽象和具体的实现,目的是分离。在分离的基础上使这些层次结合起来

模拟场景

中国有很多家银行,每家银行又有储蓄卡和信用卡。模拟聚合支付平台查询用户的金额

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 银行(抽象层聚合)
*/
public abstract class Bank {
protected Card card;

public Bank(Card card) {
this.card = card;
}

public abstract float query();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 中国农业银行
*/
public class ABCBank extends Bank{

public ABCBank(Card card) {
super(card);
}

@Override
public float query() {
System.out.println("查询中国农业银行余额");
return card.getTotal();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 中国工商银行
*/
public class ICBCBank extends Bank{

public ICBCBank(Card card) {
super(card);
}

@Override
public float query() {
System.out.println("查询中国工商银行余额");
return card.getTotal();
}
}
1
2
3
4
5
6
7
/**
* 银行卡
*/
public interface Card {
// 获取余额
float getTotal();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 储蓄卡
*/
public class BankCard implements Card{
private float debit;

public BankCard(float debit) {
this.debit = debit;
}

@Override
public float getTotal() {
System.out.println("读取储蓄卡,余额:" + this.debit);
return this.debit;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 信用卡
*/
public class CreditCard implements Card{
private float debit;

public CreditCard(float debit) {
this.debit = debit;
}

@Override
public float getTotal() {
System.out.println("读取信用卡,余额:" + this.debit);
return this.debit;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Example {
public static void main(String[] args) {
// 中国农业银行储蓄卡
ABCBank abcBank = new ABCBank(new BankCard(100.f));
abcBank.query();

// 中国农业银行信用卡
abcBank = new ABCBank(new CreditCard(500.f));
abcBank.query();

// 中国工商银行信用卡
ICBCBank icbcBank = new ICBCBank(new CreditCard(1000.f));
icbcBank.query();
}
}
// 输出
查询中国农业银行余额
读取储蓄卡,余额:100.0
查询中国农业银行余额
读取信用卡,余额:500.0
查询中国工商银行余额
读取信用卡,余额:1000.0

源码解析

JDK

JDK数据库连接(JDBC)的Driver接口,不同的数据库厂商都实现了Driver接口。通过各自的Driver都可以获取各自的数据连接Connection。数据库Driver是一个独立扩展体系,而数据库的Connection是另一个独立的扩展体系

代理

控制目标对象的访问 代理模式导致执行效率降低

定义

  • 为其他对象提供一种代理,以控制对这个对象的访问
  • 代理对象在客户端和目标对象之间起到中介的作用

适用场景

  • 保护目标对象
  • 增强目标对象

优缺点

优点

  • 代理模式能将代理对象与真实被调用的目标对象分离
  • 一定程度上降低了系统的耦合度,扩展性好
  • 保护目标对象
  • 增强目标对象

缺点

  • 代理模式会造成系统设计中类的数目增加
  • 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢
  • 增加系统的复杂度

扩展

  • 静态代理: 代理模式
  • 动态代理:InvocationHandler(无法代理类,动态代理接口)
  • CGLib代理:字节码增强(无法增强final)
  • Spring:当有实现接口时使用JDK动态代理,否则使用CGLib代理
  • 性能对比:JDK > CGLib

角色说明

  • 服务接口:声明了服务接口。 代理必须遵循该接口才能伪装成服务对象
  • 服务:实现服务接口,实现业务逻辑
  • 代理:代理对象和服务是组合关系,代理对象控制服务对象并且实现服务接口以实现对服务对象的代理

与其他设计模式对比

代理模式和装饰者模式

相同点

  • 两种从设计模式分类来看都属于结构型,因为两者均使用了组合关系。其次两者都能实现对对象方法进行增强处理的效果

不同点

  • 装饰者模式:注重的是对对象提供增强功能
  • 代理模式:对所代理对象的使用施加控制,并不提供对象本身的增强功能

代理模式和适配器模式

  • 适配器模式:主要改变所考虑对象的接口(类a转变为类b)
  • 代理模式:不能改变所考虑类的接口

源码解析

Spring

spring的代理模式在aop中有体现,如果目标类有接口实现则使用JDK动态代理JdkDynamicAopProxy否则使用CglibAopProxy

Mybatis

mybatis的Mapper接口动态代理实现

静态代理

模拟场景:给Dao层添加代理,如果本地缓存有数据则控制获取本地缓存数据,否则获取数据库数据

1
2
3
4
public interface OrderDao {
// 查询订单编号
String selectOrderOn(Integer id);
}
1
2
3
4
5
6
7
public class OrderDaoImpl implements OrderDao{
@Override
public String selectOrderOn(Integer id) {
System.out.println("数据库查询");
return UUID.randomUUID().toString();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class OrderDaoImplProxy implements OrderDao {
private Map<Integer, String> cache = new HashMap<>();
// 装饰者一般是传入一个目标对象进行增强,而代理模式则是在代理中直接创建对象
private OrderDaoImpl orderDaoImpl = new OrderDaoImpl();

private String beforeMethod(Integer id) {
String orderNo = cache.get(id);

// 施加控制,如果缓存有数据直接返回,否则查询数据库
if (orderNo != null) {
System.out.println("缓存数据返回");
return orderNo;
}
return null;
}

@Override
public String selectOrderOn(Integer id) {
String orderNo = beforeMethod(id);

// 缓存没有数据库加载
if (orderNo == null) {
orderNo = orderDaoImpl.selectOrderOn(id);
cache.put(id, orderNo);
}

afterMethod();
return orderNo;
}

private void afterMethod() {
System.out.println("执行静态代理方法");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Example {
public static void main(String[] args) {
OrderDao orderDao = new OrderDaoImplProxy();
orderDao.selectOrderOn(1);
orderDao.selectOrderOn(1);
}
}
// 输出
数据库查询
执行静态代理方法
缓存数据返回
执行静态代理方法
动态代理
  • 代理类需要实现InvocationHandler接口并且实现invoke方法
  • 通过Proxy.newProxyInstance创建代理实例

模拟场景:模拟Mybatis对Mapper接口的动态代理

1
2
3
4
5
6
7
8
/**
* 代理接口
**/
public interface UserMapper {
Map<Object, Object> selectById(Integer id);

Integer deleteById(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 代理类
* InvocationHandler 实现类
**/
public class MapperProxy implements InvocationHandler {

private MapperImpl mapperImpl = new MapperImpl();

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用Object方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}

Object result = null;
if (method.getName().startsWith("select")) {
result = this.mapperImpl.select(args[0]);
} else if (method.getName().startsWith("update")
|| method.getName().startsWith("insert")
|| method.getName().startsWith("delete")) {
result = this.mapperImpl.update(args[0]);
}

return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Mapper 通用实现类
**/
public class MapperImpl {
public Object select(Object param) {
System.out.println("执行查询语句,参数:" + param.toString());
// 模拟返回数据
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("name", UUID.randomUUID().toString());
return resultMap;
}

public Integer update(Object param) {
// 底层都是调用JDBC的 Statement.executeUpdate(sql);
System.out.println("执行 新增|修改|删除 语句,参数:" + param.toString());
// 模拟返回受影响行数
return 1;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Example {
public static void main(String[] args) {
MapperProxy proxy = new MapperProxy();

/**
* 第一个是参数是代理对象的类加载器
* 第二个参数是代理接口数组
* 第三个参数是 InvocationHandler的具体实现类,接口的所有代理实现都在 invoke 中实现
*/
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(MapperImpl.class.getClassLoader(),
new Class[]{UserMapper.class}, proxy);

Map<Object, Object> resultMap = userMapper.selectById(1);
System.out.println("selectById 返回值:" + resultMap);

Integer result = userMapper.deleteById(10);
System.out.println("deleteById 返回值:" + result);
}
}
// 输出
执行查询语句,参数:1
selectById 返回值:{name=ab1de88a-cb57-461a-acea-d111f481a7c0}
执行 新增|修改|删除 语句,参数:10
deleteById 返回值:1
CGLib动态字节码代理

CGlib的核心是 MethodInterceptor接口和 Enhancer字节码增强类

Maven依赖

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
1
2
3
4
5
public class Movie {
public void play() {
System.out.println("播放电影");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* cglib核心 MethodInterceptor 接口对方法拦截
* Enhancer 字节码增强类
* 原理,通过cglib字节码增强生成目标类的子类达到增强目的
*/
public class CglibProxy implements MethodInterceptor {

/**
* 字节码增强对象
*/
private Enhancer enhancer = new Enhancer();

/**
* 获取cglib增强对象
*
* @param clazz
* @param <T>
* @return
*/
public <T> T getProxy(Class<T> clazz) {
// 将指定类设置为增强类的父类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return (T) enhancer.create();
}

/**
* 拦截被增强类的方法,在 invokeSuper
* @param o
* @param method
* @param objects
* @param methodProxy
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("增强:播放公益广告");

// 调用父类方法,相当于重写父类方法并且在这里调用了父类的方法(super.method)
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("增强:播放彩蛋");
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Example {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();

// 获取字节码增强类
Movie movie = proxy.getProxy(Movie.class);
movie.play();
System.out.println(movie.getClass());
}
}
// 输出
增强:播放公益广告
播放电影
增强:播放彩蛋

行为型

模板方法

定义一个算法骨架,具体子类实现

定义

  • 定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现
  • 模板方法使得子类可以在不改变算法结构的情况下,重写算法的特定步骤

适用场景

  • 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
  • 各子类中公共的行为被提取出来并集中到一个公共父类中从而避免代码重复

优缺点

优点

  • 提高复用性
  • 提高扩展性

缺点

  • 类数目增加
  • 增加了系统实现的复杂度
  • 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍

扩展

  • 钩子方法:提供了缺省的行为,子类可以在必要时进行扩展

角色说明

  • 抽象类:声明作为算法步骤的方法, 以及依次调用它们的实际模板方法(算法骨架)
  • 具体类:可以重写所有步骤, 但不能重写模板方法自身

与其他设计模式对比

模板方法模式和工厂方法模式

模板方法模式和策略模式

  • 策略模式:目的是使不同的算法可以相互替换并且不影响应用层客户端的使用,可以改变算法的流程并且可以相互替换
  • 模板方法模式:针对的是定义一个算法的流程,而将一些实现步骤交给子类去实现,不改变算法的流程

模拟场景

泡茶和泡咖啡的流程大致是一样的,烧水-放调料(茶叶/咖啡粉)-装杯,咖啡有可选是否加糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 饮料(抽象类)
**/
public abstract class Drinks {
// 定义算法骨架,使用final修饰,不允许子类修改算法流程
public final void make() {
this.boilWater();
this.seasoning();
this.pourOut();

if (this.needSugar()) {
this.addSugar();
}
}

// 烧水 某个步骤如果是固化的则可以使用 final 修饰不让子类修改
protected final void boilWater() {
System.out.println("开始烧水");
}

// 调料
protected abstract void seasoning();

// 装杯
protected abstract void pourOut();

// 钩子方法 是否需要加糖
protected abstract boolean needSugar();

// 加糖
protected void addSugar() {
System.out.println("加糖");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 茶(具体类)
**/
public class Tea extends Drinks {
@Override
protected void seasoning() {
System.out.println("放茶叶");
}

@Override
protected void pourOut() {
System.out.println("装入茶杯");
}

@Override
protected boolean needSugar() {
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 咖啡(具体类)
**/
public class Coffee extends Drinks {

private boolean addSugar;
public Coffee(boolean addSugar) {
this.addSugar = addSugar;
}

@Override
protected void seasoning() {
System.out.println("放入咖啡粉");
}

@Override
protected void pourOut() {
System.out.println("装入咖啡杯");
}

@Override
protected boolean needSugar() {
return this.addSugar;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Example {
public static void main(String[] args) {
Drinks drinks = new Tea();
drinks.make();

System.out.println();
drinks = new Coffee(true);
drinks.make();
}
}
// 输出
开始烧水
放茶叶
装入茶杯

开始烧水
放入咖啡粉
装入咖啡杯
加糖

源码解析

JDK

jdk类AbstractList#addAll方法就有很多实现

Mybatis

Mybatis的Executor体系就是通过模板方法模式扩展出适应多个场景使用的执行器

BaseExecutor#query 方法

Spring

SpringAbstractApplicationContext#refresh方法实现了 IOC 容器启动的主要逻辑,不管是 XML 还是注解配置的方式,对于核心容器启动流程都是一致的

Spring Template

JDBCTemplate、RedisTemplate、MQTemplate都是使用模板方法实现的框架,它们都实现了获取连接、执行命令、释放连接这一基本流程

迭代器

迭代集合,很少自定义实现

定义

  • 提供一种方法,顺序访问一个集合对象中的各个元素,而又不暴露该对象的内部表示

适用场景

  • 访问一个集合对象的内容而无需暴露它的内部表示
  • 为遍历不同的集合结构提供一个统一的接口

优缺点

优点

  • 分离了集合对象的遍历行为

缺点

  • 类的数目增加

迭代器模式和访问者模式

  • 相同点:都是迭代的访问集合对象中的各个元素
  • 不同点
    • 访问者模式:扩展和开放的部分在作用于对象的操作上
    • 访问者模式:扩展和开放的部分是在集合的种类型

模拟场景

一个班级有多个学生,班级可以接受插班生,也可以开除顽皮的学生。上课点名班上所有同学

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 学生
**/
@ToString
public class Student {
public Student(String no, String name) {
this.no = no;
this.name = name;
}

private String no;
private String name;
}
1
2
3
4
5
6
7
8
/**
* 学生迭代器
**/
public interface StudentIterator {
Student nextStudent();

boolean isLastStudent();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 学生迭代器实现
**/
public class StudentIteratorImpl implements StudentIterator {
private List<Student> studentList;
private int position;

public StudentIteratorImpl(List<Student> classList) {
this.studentList = classList;
}

@Override
public Student nextStudent() {
if (position < studentList.size()) {
return studentList.get(position++);
}
return null;
}

@Override
public boolean isLastStudent() {
if (position == studentList.size()) {
return true;
}

return false;
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 班级
**/
public interface Class {
void addStudent(Student student);
boolean removeStudent(Student student);

// 获取迭代器
StudentIterator getIterator();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 班级实现
**/
public class ClassImpl implements Class {
private List<Student> list;

public ClassImpl() {
this.list = new ArrayList<Student>();
}

@Override
public void addStudent(Student student) {
this.list.add(student);
}

@Override
public boolean removeStudent(Student student) {
return this.list.remove(student);
}

@Override
public StudentIterator getIterator() {
return new StudentIteratorImpl(list);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Example {
public static void main(String[] args) {
Class clazz = new ClassImpl();
Student student1 = new Student("001", "张三");
Student student2 = new Student("002", "李四");
Student student3 = new Student("003", "王五");
Student student4 = new Student("004", "陆六");
Student student5 = new Student("005", "陈七");

clazz.addStudent(student1);
clazz.addStudent(student2);
clazz.addStudent(student3);
clazz.addStudent(student4);
clazz.addStudent(student5);

clazz.removeStudent(student4);
clazz.removeStudent(student5);

StudentIterator iterator = clazz.getIterator();
while (!iterator.isLastStudent()) {
System.out.println(iterator.nextStudent());
}
}
}
输出:
Student{no='001', name='张三'}
Student{no='002', name='李四'}
Student{no='003', name='王五'}

源码解析

Mybatis

Mybatis的默认游标DefaultCursor就自定义实现了迭代器CursorIterator

策略

田忌赛马,算法替换

定义

  • 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换
  • 如果代码中有很多if...else..则可以使用策略模式优雅替换

适用场景

  • 系统有很多类,而他们的区别仅仅在于他们的行为不同
  • 一个系统需要动态地在几种算法中选择一种

优缺点

优点

  • 避免使用多重条件转移语句
  • 提高算法的保密性和安全性

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
  • 产生很多策略类

角色说明

  • 上下文:维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流(算法相互替换的关键)
  • 策略接口:具体策略的通用接口, 它声明了一个上下文用于执行策略的方法
  • 具体策略:实现了上下文所用算法的各种不同变体

与其他设计模式对比

策略模式和工厂模式

  • 工厂模式:创建型模式,工厂模式接收指令,创建符合要求的具体对象

  • 策略模式:行为型模式,策略模式接收创建好的对象,从而实现不同的行为


策略模式和状态模式

  • 策略模式:客户端在使用时必须知道使用哪种策略
  • 状态模式:客户端不需要关心具体的状态,状态会自动转换

模拟场景

GPS地图导航系统的导航出行方案有步行、骑行、公交、地铁、驾车方案,使用策略模式优化大量 if…else 判断

1
2
3
4
5
6
7
/**
* 出行策略
**/
public interface Strategy {
// 出行
void setOff();
}
1
2
3
4
5
6
7
8
9
/**
* 步行出行
**/
public class WalkStrategy implements Strategy {
@Override
public void setOff() {
System.out.println("步行路线导航");
}
}
1
2
3
4
5
6
7
8
9
/**
* 骑行出行
**/
public class RideStrategy implements Strategy {
@Override
public void setOff() {
System.out.println("骑行路线导航");
}
}
1
2
3
4
5
6
7
8
9
/**
* 公交出行
**/
public class BusStrategy implements Strategy {
@Override
public void setOff() {
System.out.println("公交路线导航");
}
}
1
2
3
4
5
6
7
8
9
/**
* 地铁出行
**/
public class SubwayStrategy implements Strategy {
@Override
public void setOff() {
System.out.println("地铁路线导航");
}
}
1
2
3
4
5
6
7
8
9
/**
* 驾车出行
**/
public class DriveStrategy implements Strategy {
@Override
public void setOff() {
System.out.println("驾车路线导航");
}
}
1
2
3
4
5
6
7
8
9
/**
* 空策略,用于兼容
**/
public class EmptyStrategy implements Strategy {
@Override
public void setOff() {
System.out.println("空策略,兼容");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 上下文
*/
public class StrategyContext {
private Strategy strategy;

public StrategyContext(Strategy strategy) {
this.strategy = strategy;
}

public void executeStrategy() {
strategy.setOff();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 策略工厂
* 缓存工厂
**/
public class StrategyFactory {
private StrategyFactory() {
}

private static final Map<String, Strategy> STRATEGY_CONTAINER = new HashMap<>();

// 初始化
static {
STRATEGY_CONTAINER.put(null, new EmptyStrategy());
STRATEGY_CONTAINER.put(StrategyKey.WALK, new WalkStrategy());
STRATEGY_CONTAINER.put(StrategyKey.RIDE, new RideStrategy());
STRATEGY_CONTAINER.put(StrategyKey.BUS, new BusStrategy());
STRATEGY_CONTAINER.put(StrategyKey.SUBWAY, new SubwayStrategy());
STRATEGY_CONTAINER.put(StrategyKey.DRIVE, new DriveStrategy());
}

public static Strategy getStrategy(String strategyKey) {
Strategy strategy = STRATEGY_CONTAINER.get(strategyKey);
if (Objects.isNull(strategy)) {
strategy = STRATEGY_CONTAINER.get(null);
}

return strategy;
}

interface StrategyKey {
String WALK = "WALK";
String RIDE = "RIDE";
String BUS = "BUS";
String SUBWAY = "SUBWAY";
String DRIVE = "DRIVE";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Example {
public static void main(String[] args) {
String key = StrategyFactory.StrategyKey.BUS;
StrategyContext context = new StrategyContext(StrategyFactory.getStrategy(key));
context.executeStrategy();

// 算法替换
key = StrategyFactory.StrategyKey.SUBWAY;
context = new StrategyContext(StrategyFactory.getStrategy(key));
context.executeStrategy();
// 输出
// 公交路线导航
// 地铁路线导航
}
}

源码详解

JDK

JDK的排序接口Comparator就是使用策略模式进行各种类型的数据排序,Collections#sort方法就是要传递一个自定义的Comparator<? super T>对集合进行排序

Spring

InstantiationStrategy 接口定义了 Spring Bean 实例化的策略,有简单通过构造函数或者方法工厂创建的,也有通过使用Cglib字节码增强创建的

解释器

非常低频使用

定义

  • 给定一个语言,定义它的文法的一种表示,并定义一个解释器这个解释器使用该表示来解释语言中的句子
  • 为了解释一种语言,而为语言创建的解释器

适用场景

  • 某个特定类型问题发生频率足够高

优缺点

优点

  • 语法由很多类表示,容易改变及扩展此语言

缺点

  • 当语法规则数目太多时,增加了系统复杂度

解释器模式和适配器模式

  • 适配器:不需要事先知道要适配的规则
  • 解释器:需要事先写好规则,根据规则去进行解释

模拟场景

实现一个简单的加减法数学公式计算器,比如计算3 + 10这个字符串的和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 表达式
**/
public interface Expression {
// 解释
int interpret(String str);

static List<Integer> parse(String str) {
List<String> collect = Arrays.stream(str.split(" "))
.filter(line -> {
if ("".equals(line) || null == line) {
return false;
}
return true;
}).collect(Collectors.toList());

return Arrays.asList(Integer.valueOf(collect.get(0)), Integer.valueOf(collect.get(2)));
}
}
1
2
3
4
5
6
7
8
9
10
11
/**
* 加法表达式
**/
public class AddExpression implements Expression {

@Override
public int interpret(String str) {
List<Integer> list = Expression.parse(str);
return list.get(0) + list.get(1);
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 减法表达式
**/
public class SubExpression implements Expression {
@Override
public int interpret(String str) {
List<Integer> list = Expression.parse(str);
return list.get(0) - list.get(1);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 工具类
**/
public class OperatorUtil {
public static int operator(String str) {
Expression expression = null;
if (str.contains("+")) {
expression = new AddExpression();
} else if (str.contains("-")) {
expression = new SubExpression();
}
return expression.interpret(str);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Example {
public static void main(String[] args) {
String str = "2 + 3";
System.out.println(str + " 解释器解释结果:" + OperatorUtil.operator(str));

str = "3 - 7";
System.out.println(str + " 解释器解释结果:" + OperatorUtil.operator(str));
}
}
// 输出
2 + 3 解释器解释结果:5
3 - 7 解释器解释结果:-4

源码解析

JDK

JDK工具类Pattern用于解释正则表达式语法

Spring

Spring的EL表达式也是使用解释器进行表达式解释

1
2
3
4
5
6
7
8
9
public class Example {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("10 + 10 * 10");
Integer result = expression.getValue(Integer.class);
System.out.println(result);
// 输出 110
}
}

观察者

一对多 订阅机制

定义

  • 定义了对象之间的一对多依赖,让多个观察者对象同时监听某个主题对象,当主题对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新

适用场景

  • 关联行为场景,建立一套触发机制

优缺点

优点

  • 观察者和被观察者之间建立一个抽象的耦合
  • 观察者模式支持广播通信

缺点

  • 观察者之间有过多的细节依赖、提高时间消耗及程序复杂度
  • 使用要得当,要避免循环调用

角色说明

  • 发布者:会向其他对象发送值得关注的事件。 事件会在发布者自身状态改变或执行特定行为后发生。 发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架
  • 订阅者:接口声明了通知接口。 在绝大多数情况下, 该接口仅包含一个 update 更新方法
  • 具体订阅者:可以执行一些操作来回应发布者的通知。 所有具体订阅者类都实现了同样的接口, 因此发布者不需要与具体类相耦合

模拟场景

微信群聊天,聊天室是消息发布者,微信用户是订阅者(登录),微信客户端是具体订阅者(展示消息)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 聊天室 (发布者)
**/
public class Chatroom {

// 当前消息
private String currentMsg;

// 聊天室成员
private List<WxUser> userList = new CopyOnWriteArrayList<>();

// 添加成员
public void addUser(Observer observer) {
this.userList.add(observer);
}

// 发送群消息
public void sendMsg(String msg) {
this.currentMsg = msg;
// 消息通知
this.notifyAllObservers();
}

// 群消息通知
public void notifyAllObservers() {
for (WxUser wxUser : userList) {
wxUser.receive(this.currentMsg);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 微信用户(订阅者)
**/
public abstract class WxUser {
private Chatroom chatroom;

public WxUser(Chatroom chatroom) {
this.chatroom = chatroom;
// 把自己加入观察者队列
chatroom.addUser(this);
}

// 接收信息
public abstract void receive(String msg);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 微信客户端(具体订阅者)
**/
public class WxClient extends WxUser {
public WxClient(Chatroom chatroom) {
super(chatroom);
}

@Override
public void receive(String msg) {
System.out.println(this.toString() + ": 接收到群消息:" + msg);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Example {
public static void main(String[] args) {
Chatroom chatroom = new Chatroom();

new WxUser(chatroom);
new WxUser(chatroom);
new WxUser(chatroom);
new WxUser(chatroom);
new WxUser(chatroom);

chatroom.sendMsg("Hello!");
}
}
// 输出
WxUser@4f933fd1: 接收到群消息:Hello!
WxUser@548a9f61: 接收到群消息:Hello!
WxUser@1753acfe: 接收到群消息:Hello!
WxUser@7c16905e: 接收到群消息:Hello!
WxUser@2a2d45ba: 接收到群消息:Hello!

源码解析

Spring

Spring的事件体系就是使用观察者模式,可以通过自定义事件 事件发布者(主题) 事件监听器(观察者)

参考:https://segmentfault.com/a/1190000037666661

Zookeeper

zk客户端对服务端的某个节点进行监听,也是使用观察者思想

备忘录

快照数据,可进行撤回操作 DB的binlog日志 IDEA的撤回功能

定义

  • 保存一个对象的某个状态,以便在适当的时候恢复对象

适用场景

  • 保存及恢复数据相关业务场景
  • 后悔的时候,即想恢复到之前的状态

优缺点

优点

  • 为用户提供一种可恢复机制
  • 存档信息的封装

缺点

  • 资源占用,如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存

与其他设计模式对比

备忘录模式和状态模式

  • 备忘录模式:使用实例表示状态,我们的存档是对象的一个实例
  • 状态模式:用类表示状态

模拟场景

论坛发表留言时,可以暂存草稿。下次重新进入当前主题后又可以重新加载暂存草稿继续发言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 留言消息
*/
@Data
@ToString
public class Message {

public Message(String title, String content) {
this.title = title;
this.content = content;
}

private String title;
private String content;

// 进行快照
public MessageSnapshot snapshot() {
return new MessageSnapshot(this.title, this.content);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 留言消息快照,快照数据不提供set方法,不允许修改
*/
@Getter
public class MessageSnapshot {

public MessageSnapshot(String title, String content) {
this.title = title;
this.content = content;
}

private String title;
private String content;

// 快照数据进行转换
public Message change() {
return new Message(this.title, this.content);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 消息快照管理
*/
public class MessageSnapshotManager {
private final Stack<MessageSnapshot> snapshotContainer = new Stack<>();

public void save(MessageSnapshot messageSnapshot) {
this.snapshotContainer.push(messageSnapshot);
}

public MessageSnapshot getSnapshot() {
return this.snapshotContainer.pop();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Example {
public static void main(String[] args) {
Message message = new Message("title A", "回复内容 A");

// 快照,保存草稿
MessageSnapshot snapshot = message.snapshot();
MessageSnapshotManager manager = new MessageSnapshotManager();
manager.save(snapshot);

System.out.println("记录草稿,离开页面");
message = null;

System.out.println("重新进入页面获取草稿");
snapshot = manager.getSnapshot();
message = snapshot.change();
System.out.println(message);
}
}
// 输出
记录草稿,离开页面
重新进入页面获取草稿
Message(title=title A, content=回复内容 A)

源码解析

JDK

  • JDK的序列化Serializable,可以将一个对象进行序列化后保存在磁盘的文件中,待下次使用时进行反序列化

Mysql

  • Mysql每次提交事务前都会记录 binlog 以便于事务回滚

命令

Linux命令执行 操作参数化

定义

  • 将请求转换为一个包含与请求相关的所有信息的独立对象,该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作
  • 命令模式解决了应用程序中对象的职责以及它们之间的通信方式

适用场景

  • 如果你需要通过操作来参数化对象, 可使用命令模式(将操作封装成独立对象,对象作为参数传递)
  • 如果你想要将操作放入队列中、 操作的执行或者远程执行操作, 可使用命令模式
  • 如果你想要实现操作撤回功能, 可使用命令模式

优缺点

优点

  • 降低耦合
  • 容易扩展新命令或者一组命令

缺点

  • 命令的无限扩展会增加类的数量,提高系统实现复杂度

角色说明

  • 命令接口:通常仅声明一个执行命令的方法
  • 具体命令:会实现各种类型的请求。 具体命令自身并不完成工作, 而是会将调用委派给一个业务逻辑对象
  • 接收者类:包含部分业务逻辑。 几乎任何对象都可以作为接收者。 绝大部分命令只处理如何将请求传递到接收者的细节, 接收者自己会完成实际的工作
  • 发送者类:负责对请求进行初始化, 其中必须包含一个成员变量来存储对于命令对象的引用。 发送者触发命令, 而不向接收者直接发送请求

与其他设计模式对比

命令模式和备忘录模式

命令模式和备忘录模式经常结合使用,可以使用备忘录模式保存命令的历史记录,这样我们就可以直接调取上一个命令或历史执行过的命令(Linux的控制台)


模拟场景

模拟小度的语音交互,回家时使用语音命令让小度打开电灯,睡觉时让小度关闭电灯

1
2
3
4
5
6
/**
* 命令接口
*/
public interface Command {
void execute();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 开灯命令(具体命令)
*/
public class LightOnCommand implements Command {
private Light light;
// 语音
private String voice;

public LightOnCommand(Light light, String voice) {
this.light = light;
this.voice = voice;
}

@Override
public void execute() {
System.out.println("接收语音命令:" + voice);
light.on();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 关灯命令(具体命令)
*/
public class LightOffCommand implements Command {
private Light light;
// 语音
private String voice;

public LightOffCommand(Light light, String voice) {
this.light = light;
this.voice = voice;
}

@Override
public void execute() {
System.out.println("接收语音命令:" + voice);
light.off();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 电灯(接收者类)
* 命令模式好处是随着电灯操作的增加,只需要扩展相应的Command
* 客户端使用不用发生改变
*/
public class Light {
public void on() {
System.out.println("接收开灯命令,正在打开电灯");
}

public void off() {
System.out.println("接收段等命令,正在关闭电灯");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 小度智能语音(发送者)
*/
public class XiaoDu {
private List<Command> commandList = new ArrayList<>();

// 接收命令
public void receiveCommand(Command command) {
this.commandList.add(command);
}

public void executeCommands() {
for (Command command : commandList) {
command.execute();
}
// 命令执行完成后清除
commandList.clear();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Example {
public static void main(String[] args) {
Light light = new Light();
XiaoDu xiaoDu = new XiaoDu();

// 回家
xiaoDu.receiveCommand(new LightOnCommand(light, "小度小度,开灯"));
// 可以一次性执行多条命令
xiaoDu.executeCommands();

// 准备睡觉
xiaoDu.receiveCommand(new LightOffCommand(light, "小度小度,关灯"));
xiaoDu.executeCommands();
}
}
//输出
接收语音命令:小度小度,开灯
接收开灯命令,正在打开电灯
接收语音命令:小度小度,关灯
接收段等命令,正在关闭电灯

源码解析

JDK

JDK的Runnable接口其实就是一个抽象的命令,实现它的过程其实就是定义了一系列命令后,让线程(新建或线程池的线程)去执行你定义的命令(实现的run方法)

中介者

整理无序的依赖关系 一对多转一对一

定义

能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作


适用场景

  • 当一些对象和其他对象紧密耦合以致难以对其进行修改时, 可使用中介者模式
  • 当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式
  • 如果为了能在不同情景下复用一些基本行为, 导致你需要被迫创建大量组件子类时, 可使用中介者模式

优缺点

优点

  • 将一对多转化成了一对一、降低程序复杂度
  • 类之间解耦

缺点

  • 中介者过多,导致系统复杂

角色说明

  • 组件:是各种包含业务逻辑的类。 每个组件都有一个指向中介者的引用, 该引用被声明为中介者接口类型
  • 中介者接口:声明了与组件交流的方法, 但通常仅包括一个通知方法。 组件可将任意上下文 (包括自己的对象) 作为该方法的参数, 只有这样接收组件和发送者类之间才不会耦合
  • 具体中介者:封装了多种组件间的关系。 具体中介者通常会保存所有组件的引用并对其进行管理, 甚至有时会对其生命周期进行管理

与其他设计模式对比

中介者模式和观察者模式

某些场景这两种模式会结合适用,使用观察者模式实现中介者模式角色之间的通讯


模拟场景

手机有闹钟,音乐播放,视频播放功能,手机就像这些功能的中介者整合了这些功能。比如我们可以设置一个闹钟提醒我们及时观看时下正在热播的连续剧

1
2
3
4
5
6
/**
* 功能(抽象组件)
*/
public interface Function {
void onEvent(Mobile mobile);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 闹钟功能(具体组件)
*/
public class AlarmFunction implements Function {
@Override
public void onEvent(Mobile mobile) {
// 使用中介者
mobile.doEvent("alarm");
}

public void alarm() {
System.out.println("闹钟触发时间到");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 放音乐功能(具体组件)
*/
public class MusicFunction implements Function {
@Override
public void onEvent(Mobile mobile) {
// 使用中介者
mobile.doEvent("music");
}

public void playMusic() {
System.out.println("播放音乐");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 看视频功能(具体组件)
*/
public class VideoFunction implements Function {
@Override
public void onEvent(Mobile mobile) {
// 使用中介者
mobile.doEvent("video");
}

public void playVideo() {
System.out.println("播放视频");
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 中介(具体中介者)
*/
public class Mobile {
private AlarmFunction alarmFunction;
private MusicFunction musicDemand;
private VideoFunction videoDemand;

public Mobile(AlarmFunction alarmFunction, MusicFunction musicDemand, VideoFunction videoDemand) {
this.alarmFunction = alarmFunction;
this.musicDemand = musicDemand;
this.videoDemand = videoDemand;
}

public void doEvent(String type) {
switch (type) {
case "alarm":
doAlarmEvent();
break;
case "video":
doVideoEvent();
break;
}
}

// 提醒及时观看连续剧,通过中介者完成多个组件的依赖调用
private void doAlarmEvent() {
alarmFunction.alarm();
musicDemand.playMusic();
videoDemand.playVideo();
}

private void doVideoEvent() {
// TODO 看视频设置音量
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Example {
public static void main(String[] args) {
AlarmFunction alarmFunction = new AlarmFunction();
MusicFunction musicFunction = new MusicFunction();
VideoFunction videoFunction = new VideoFunction();
Mobile mobile = new Mobile(alarmFunction, musicFunction, videoFunction);

// 闹钟提醒观看连续剧(通过中介者操作其他组件)
alarmFunction.onEvent(mobile);
}
}
// 输出
闹钟触发时间到
播放音乐
播放视频

源码解析

JDK

JDK的反射机制我们非常熟悉,Method#invoke可以反射调用某个对象的方法,实际上调用的是第一个参数obj对象的一个成员方法,而非Method实现方法。Method在反射中起到中介作用

责任链

链式执行

定义

  • 为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链

  • 当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止


适用场景

  • 当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时, 可以使用责任链模式
  • 当必须按顺序执行多个处理者时, 可以使用该模式
  • 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式

优缺点

优点

  • 请求的发送者和接收者(请求的处理)解耦
  • 责任链可以动态组合

缺点

  • 责任链太长或者处理时间过长,影响性能
  • 责任链有可能过多

与其他设计模式对比

责任链模式和状态模式

  • 责任链模式:在责任链模式中,各个对象并不指定下一个处理的对象者是谁,只有在客户端中设置了处理对象的顺序,直到被某个对象处理或整个链条结束
  • 状态模式:每个状态对象知道自己下一个处理的对象是谁

模拟场景

网站用户注册时,校验用户名是否为一个正确的邮箱格式,密码长度是否大于6位,否则就抛出异常让用户重新填写

1
2
3
4
5
6
7
8
9
/**
* 用户
*/
@Data
@AllArgsConstructor
public class User {
private String username;
private String password;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 验证处理器
*/
public abstract class VerifyHandler {
private VerifyHandler next;

public void verify(User user) {
handler(user);
if (Objects.nonNull(this.next)) {
this.next.verify(user);
}
}

// 处理
protected abstract void handler(User user);

public void setNext(VerifyHandler next) {
this.next = next;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 邮箱验证
*/
public class EmailHandler extends VerifyHandler {

@Override
protected void handler(User user) {
if (Objects.isNull(user.getUsername())) {
throw new NullPointerException("邮箱为空");
}

String str = "^([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)*@([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)+[\\.][A-Za-z]{2,3}([\\.][A-Za-z]{2})?$";
Pattern p = Pattern.compile(str);
Matcher m = p.matcher(user.getUsername());

if (!m.matches()) {
throw new IllegalArgumentException("邮箱格式不合法");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 密码验证
*/
public class PasswordHandler extends VerifyHandler {
@Override
protected void handler(User user) {
if (Objects.isNull(user.getPassword())) {
throw new NullPointerException("密码不能为空");
}

if (user.getPassword().length() < 6) {
throw new IllegalArgumentException("密码长度必须超过6位");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Example {
public static void main(String[] args) {
// 创建责任链
VerifyHandler emailHandler = new EmailHandler();
VerifyHandler passwordHandler = new PasswordHandler();
emailHandler.setNext(passwordHandler);

User user = new User("test@test.com", "123456");
emailHandler.verify(user);

// 输出 Exception in thread "main" java.lang.IllegalArgumentException: 密码长度必须超过6位
}
}

源码解析

Spring MVC

Spring MVC 源码中大量使用了责任链模式,比如请求->方法调用的映射,还有自定义拦截器HandlerInterceptor

DispatcherServlet#doDispatch在处理请求时会先获取拦截器链

Spring security

Spring security的权限验证体系也是使用责任链完成的

访问者

低频使用 数据的操作被重定向到访问者 数据结构与数据操作分离

定义

  • 它能将算法与其所作用的对象隔离开来

适用场景

  • 如果你需要对一个复杂对象结构中的所有元素执行某些操作, 可使用访问者模式
  • 当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式
  • 数据结构与数据操作分离

优缺点

优点

  • 增加新的操作很容易,即增加一个新的访问者

缺点

  • 增加新的数据结构困难
  • 具体元素变更比较麻烦

角色说明

  • 元素接口:声明了一个方法来 “接收” 访问者。 该方法必须有一个参数被声明为访问者接口类型
  • 具体元素:必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法
  • 访问者接口:声明了一系列以对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的
  • 具体访问者:会为不同的具体元素类实现相同行为的几个不同版本

与其他设计模式对比

访问者模式和迭代器模式

  • 共同点:都是在某种数据结构上进行一些处理

  • 访问者:主要对保存在数据结构中的元素进行某种特定的处理,重点是处理

  • 迭代器:主要是逐个遍历保存在数据结构中的一些元素,重点是遍历


模拟场景

计算机零件有键盘、鼠标、内存、磁盘。电脑可以访问这些零件执行一些操作

1
2
3
4
5
6
7
/**
* 电脑零件(元素接口)
*/
public interface ComputerPart {
// 接受访问
void accept(Visitor visitor);
}
1
2
3
4
5
6
7
8
9
10
/**
* 键盘(具体元素)
*/
public class Keyboard implements ComputerPart {
@Override
public void accept(Visitor visitor) {
// 调用访问者进行访问
visitor.visit(this);
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 鼠标(具体元素)
*/
public class Mouse implements ComputerPart {
@Override
public void accept(Visitor visitor) {
// 调用访问者进行访问 内存
visitor.visit(this);
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 内存(具体元素)
*/
public class Memory implements ComputerPart {
@Override
public void accept(Visitor visitor) {
// 调用访问者进行访问
visitor.visit(this);
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 磁盘(具体元素)
*/
public class Disk implements ComputerPart {
@Override
public void accept(Visitor visitor) {
// 调用访问者进行访问
visitor.visit(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 访问者(访问者接口)
*/
public interface Visitor {

// 访问键盘
void visit(Keyboard keyboard);

// 访问鼠标
void visit(Mouse mouse);

// 访问内存
void visit(Memory memory);

// 访问磁盘
void visit(Disk disk);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 电脑(具体访问者)
*/
public class Computer implements Visitor {
@Override
public void visit(Keyboard keyboard) {
System.out.println("访问键盘 亮起键盘灯");
}

@Override
public void visit(Mouse mouse) {
System.out.println("访问鼠标 亮起鼠标灯");
}

@Override
public void visit(Memory memory) {
System.out.println("访问内存 写入内存数据");
}

@Override
public void visit(Disk disk) {
System.out.println("访问磁盘 写入文件");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Example {
public static void main(String[] args) {
List<ComputerPart> list = new ArrayList<>();
list.add(new Keyboard());
list.add(new Mouse());
list.add(new Memory());
list.add(new Disk());

// 访问者,实现访问不同数据结构的操作
Visitor visitor = new Computer();

for (ComputerPart computerPart : list) {
computerPart.accept(visitor);
}

// 输出
// 访问键盘 亮起键盘灯
// 访问鼠标 亮起鼠标灯
// 访问内存 写入内存数据
// 访问磁盘 写入文件
}
}

源码解析

JDK

JDK nio包下的FileVisitor文件访问者接口,提供文件的访问和遍历操作

Spring

Spring的BeanDefinitionVisitor用于遍历bean中的属性值和构造参数值,解析bean的元数据值

状态

将状态封装成对象并处理特定逻辑,上下文简化调用。实际业务处理由上下文重定向到状态对象

定义

允许一个对象在其内部状态改变时,改变它的行为


适用场景

  • 如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式
  • 如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式
  • 当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式

优缺点

优点

  • 将不同的状态隔离
  • 把各种状态的转换逻辑,分布到State的子类中,减少相互间依赖
  • 增加新的状态非常简单

缺点

  • 状态多的业务场景导致类数目增加,系统变复杂

角色说明

  • 状态接口:声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用
  • 具体状态:会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类
  • 上下文:保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象

与其他设计模式对比

状态模式和享元模式

状态模式和享元模式在特定场景可以配合使用,使用享元模式在多个上下文之间共享状态实例


场景模拟

地铁自定义有五种状态:运行、停止、开门、关门、异常、,每个状态执行不同的方法能够改变状态,并且不同的状态只能能执行特定的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 基础状态(状态接口)
*/
public abstract class State {
// 状态持有上下文,当状态改变时才能委托给上下文
private Subway context;

public void setContext(Subway context) {
this.context = context;
}

// 以下方法都是帮助上下文切换状态并触发上下文执行
public void run() {
// 切换上下文状态
this.context.setState(Subway.RUN_STATE);
// 将业务逻辑委托给上下文,上下文再委托给当前状态
this.context.run();
}

public void stop() {
this.context.setState(Subway.STOP_STATE);
this.context.stop();
}

public void open() {
this.context.setState(Subway.OPEN_STATE);
this.context.open();
}

public void close() {
this.context.setState(Subway.CLOSE_STATE);
this.context.close();
}

public void error() {
this.context.setState(Subway.ERROR_STATE);
this.context.error();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 地铁运行状态(具体状态)
*/
public class RunState extends State {
@Override
public void run() {
System.out.println("地铁运行,前往下一个地铁站");
}

/**
* 控制 run -> open 不能切换,如果切换则跳转到异常状态
*/
@Override
public void open() {
super.context.setState(Subway.ERROR_STATE);
super.context.open();
}
}
1
2
3
4
5
6
7
8
9
/**
* 地铁停止状态(具体状态)
*/
public class StopState extends State {
@Override
public void stop() {
System.out.println("地铁进站");
}
}
1
2
3
4
5
6
7
8
9
/**
* 地铁开门状态(具体状态)
*/
public class OpenState extends State {
@Override
public void open() {
System.out.println("地铁开门");
}
}
1
2
3
4
5
6
7
8
9
/**
* 地铁停止状态(具体状态)
*/
public class CloseState extends State {
@Override
public void close() {
System.out.println("地铁关门");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 地铁异常状态(具体状态)
*/
public class ErrorState extends State {

@Override
public void run() {
System.out.println("异常警告!,不能启动");
}

@Override
public void stop() {
System.out.println("异常警告!,不能停止");
}

@Override
public void open() {
System.out.println("异常警告!,不能开门");
}

@Override
public void close() {
System.out.println("异常警告!,不能关门");
}

@Override
public void error() {
System.out.println("异常警告!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 地铁 (上下文)
*/
public class Subway {
// 定义状态
public final static RunState RUN_STATE = new RunState();
public final static StopState STOP_STATE = new StopState();
public final static OpenState OPEN_STATE = new OpenState();
public final static CloseState CLOSE_STATE = new CloseState();
public final static ErrorState ERROR_STATE = new ErrorState();


// 当前地铁状态
private State state;

public void setState(State state) {
// 切换上下文状态
this.state = state;
// 设置上下文
state.setContext(this);
}

// 以下方法上下文都会委托给当前状态处理
public void run() {
this.state.run();
}

public void stop() {
this.state.stop();
}

public void open() {
this.state.open();
}

public void close() {
this.state.close();
}

public void error() {
this.state.error();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Example {
public static void main(String[] args) {
// 初始化上下文
Subway context = new Subway();
context.setState(Subway.STOP_STATE);

// 上下文状态切换,具体实现由对应的State实现
context.open();
context.close();
context.run();
context.open();

// 输出
// 地铁开门
// 地铁关门
// 地铁运行,前往下一个地铁站
// 异常警告!,不能开门
}
}

源码解析

状态模式一般结合业务模式开发


设计模式
https://wugengfeng.cn/2022/04/23/设计模式/
作者
wugengfeng
发布于
2022年4月23日
许可协议