设计模式

设计模式是什么?

设计模式是我们对问题所提出的解决方案,就像一个个蓝图,通过对问题的一些综合考虑,采用最合适的设计方案来解决问题。就像一个工具箱,我们要看具体的情况,来决定使用哪把工具。那么设计模式是如何诞生的呢,设计模式最开始也是一个解决方案,只不过这个方案在各种项目中得到了验证。最终得到认可,是前辈们一个个试验,一步一个坑踩过来,最终被后人们整理,收纳,所归类的出的一种新领域

设计模式的优点

  • 提高我们的思维能力和设计能力
  • 使程序的设计变得标准化、流程化,增强开发效率
  • 对代码来说,提高了可读性和复用性以及可扩展性

设计模式的六大原则

  1. 单一职责: 一个类应该只有一个会引起它变化的原因,也就是一个类只负责一个职责
  2. 开闭原则: 对扩展开放,对修改关闭
  3. 里氏代换原则: 子类应该可以替换父类对象,并保持逻辑不变
  4. 依赖倒转原则: 抽象不依赖细节,细节依赖于抽象。也就是对接口编程,不要直接使用实现类
  5. 接口隔离原则: 不应该强迫一个类实现它不需要的方法,而是使用多个精细化的接口
  6. 迪米特法则: 一个实体类尽量少与其他实体类有相互作用

设计模式的分类

创建型模式:通过提供创建对象的机制,增加已有代码的灵活性和可复用性

创建型有种模式:工厂方法、抽象工厂、建造者、原型、单例

结构型模式:如何将对象和类组装成较大的结构,同时保持结构的灵活和高效

结构型有种模式:适配器、桥接、组合、装饰、外观、享元、代理

行为型模式:负责对象间的高效沟通和职责委派

行为型有十一种模式:责任链、命令、迭代器、解释器、中介者、备忘录、观察者、状态、策略、模板方法、访问者

现在我们对设计模式有了初步认识,下面我们对每一种设计模式进行详细了解,并逐一举例

PS:本文缓慢更新(当你看到这句话的时候表明没有更新完),更新顺序会参考我们平时的使用频率以及重要程度随缘更新

创建型

工厂方法(Factory Method)

在讲解工厂方法之前,我们先来了解一下简单工厂,简单工厂是由一个接口、多个接口实现类以及一个工厂类组成,我们以游戏平台举例,平台就是一个接口,而Steam、Epic等就是具体的实现类,工厂类则是负责创建实现类的地方。我们具体来看代码:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 接口类
public interface Platform {
void print();
}

// 第一个实现类
public class Epic implements Platform {
@Override
public void print() {
System.out.println("我是Epic平台");
}
}

// 第二个实现类
public class Origin implements Platform {
@Override
public void print() {
System.out.println("我是橘子平台");
}
}

// 第三个实现类
public class Steam implements Platform {
@Override
public void print() {
System.out.println("我是Steam平台");
}
}

// 工厂类
public class GamePlatform {
public Platform getPlatform(String platform) {
if ("Epic".equalsIgnoreCase(platform)) {
return new Epic();
} else if ("Steam".equalsIgnoreCase(platform)) {
return new Steam();
} else if ("Origin".equalsIgnoreCase(platform)) {
return new Origin();
}
return null;
}
}

// 使用简单工厂
public class Main {
public static void main(String[] args) {
String param = "steam";
// 创建工厂类实例
GamePlatform gamePlatform = new GamePlatform();

Platform steam = gamePlatform.getPlatform(param);
steam.print();
param = "epic";
Platform epic = gamePlatform.getPlatform(param);
epic.print();
}
}

// 控制台输出
-- 我是Steam平台
-- 我是Epic平台

如果我们不使用简单工厂来完成这个案例,我们会直接new Steam(), new Epic()来完成对象的创建,那我们为什么要把创建对象的任务交给工厂类呢?因为简单工厂提供了解耦、维护性和灵活性。如果我们不使用简单工厂,那么当一个对象的构造逻辑发生改变,例如需要传入一些固定参数,我们需要在每一个用到的地方去修改并测试,但如果是简单工厂,我们直接在工厂类内进行修改即可,这便是解耦和维护。灵活性则表现在扩展,如果后续我们需要新加平台,没有使用简单工厂的情况下,我们需要去每一个地方去手动新增一个平台,而简单工厂只需要新增一个实现类,随后在工厂类内新加一个条件即可,通过参数来控制工厂类获取的具体实现对象。但这样破坏了开闭原则(对扩展开放,对修改关闭),所以工厂方法就这样出来了。

工厂方法在简单工厂的基础上做了一些修改,将原本一个工厂拆开了,每个对象都有属于自己的工厂。这次我们以支付举例,具体来看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 支付接口
public interface Pay {
void pay();
}

// 支付宝支付类
public class AliPay implements Pay {
@Override
public void pay() {
System.out.println("用户使用AliPay支付,开始执行具体逻辑……");
}
}

// 微信支付类
public class WechatPay implements Pay {
@Override
public void pay() {
System.out.println("用户使用微信支付,开始执行具体逻辑……");
}
}

到目前为止都是跟简单工厂一样,工厂部分可以看到阿里云和微信都有对应的工厂且继承了一个抽象类,这个抽象类就是负责声明一个创建工厂的方法。具体工厂代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 支付工厂 所以支付对象工厂都要继承 相当于父类
public abstract class PayFactory {
public abstract Pay getPayType();
}

// 阿里云支付工厂
public class AliPayFactory extends PayFactory {
@Override
public Pay getPayType() {
return new AliPay();
}
}

// 微信支付工厂
public class WechatPayFactory extends PayFactory {
@Override
public Pay getPayType() {
return new WechatPay();
}
}

之后通过参数来控制工厂的创建并调用支付方法,以后要新增支付方法,只需要实现一个新的支付类,创建一个新的支付工厂即可,解决了简单工厂的弊端(新增内容需要修改工厂类),符合开闭原则。下面是具体的客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
public static void main(String[] args) {
// 支付参数
String param = "ali";

PayFactory factory = null;
if ("wechat".equals(param)) {
factory = new WechatPayFactory();
} else if ("ali".equals(param)) {
factory = new AliPayFactory();
}

Pay payType = factory.getPayType();
payType.pay();
}
}

// 控制台输出
-- 用户使用AliPay支付,开始执行具体逻辑……

适用场景:开发与线上环境的快速切换,数据库连接类型,多类型文件生成与读取等等

建造者(Builder)

建造者模式更注重构建对象的这个过程,通过分步创建一个复杂的对象,将产品的创建与产品的本身进行分离,构建的过程就可以获得不同的对象。在代码中使用链式调用,方便的同时增加了可读性。我们一般通过建造者模式来创建那些有非传参数的对象或者参数过多的对象。

相信大家肯定见过类似于下面这样的代码,一个类的构造函数有多个可选参数,为了保证正常调用会写多个方法来进行重载,在这种情况下,我们就可以使用建造者的设计模式来完成。

1
2
3
4
public class Computer {
Computer(String cpu) { …… }
Computer(String cpu, String gpu) { …… }
Computer(String cpu, String gpu, String board) { …… }

这里我们假设cpu、gpu、board和arm是必传参数。使用建造者模式:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class Computer {
private String cpu;
private String gpu;
private String board;
private String arm;
private String ssd;
private String power;

public Computer(ComputerBuilder computerBuilder) {
this.cpu = computerBuilder.cpu;
this.gpu = computerBuilder.gpu;
this.board = computerBuilder.board;
this.arm = computerBuilder.arm;
this.ssd = computerBuilder.ssd;
this.power = computerBuilder.power;
}

public static class ComputerBuilder{
private final String cpu;
private final String gpu;
private final String board;
private final String arm;
private String ssd;
private String power;

public ComputerBuilder(String cpu, String gpu, String board, String arm) {
this.cpu = cpu;
this.gpu = gpu;
this.board = board;
this.arm = arm;
}

public ComputerBuilder setSsd(String ssd) {
this.ssd = ssd;
return this;
}

public ComputerBuilder setPower(String power) {
this.power = power;
return this;
}

public Computer build() {
return new Computer(this);
}
}

@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", gpu='" + gpu + '\'' +
", board='" + board + '\'' +
", arm='" + arm + '\'' +
", ssd='" + ssd + '\'' +
", power='" + power + '\'' +
'}';
}
}

// 客户端代码
public class Main {
public static void main(String[] args) {
Computer computerBuilder = new Computer
.ComputerBuilder("7800x", "7900xtx", "B660M", "32G")
.build();
Computer computerBuilder2 = new Computer
.ComputerBuilder("7800x", "7900xtx", "B660M", "32G")
.setSsd("2T")
.setPower("1000w")
.build();
System.out.println(computerBuilder);
System.out.println(computerBuilder2);
}
}

// 控制台输出
-- Computer{cpu='7800x', gpu='7900xtx', board='B660M', arm='32G', ssd='null', power='null'}
-- Computer{cpu='7800x', gpu='7900xtx', board='B660M', arm='32G', ssd='2T', power='1000w'}

电脑类的所有属性都要经过ComputerBuilder来完成,并且在内部配置了必选参数和可选参数,必选参数保证了对象创建的完整性同时避免了构造函数爆炸的情况,可选参数带来了扩展性与灵活性,可以随时新增字段而不影响现有代码,链式调用又直观体现了该对象的整体结构

适用场景:复杂对象的创建,数据库连接参数配置,http连接参数的配置等等

结构型

适配器(Adapter)

适配器模式用于将一个类的接口转为期望的一个接口,来使原本不兼容的接口可以正常工作,适配器的核心是为现有类提供一个兼容的接口,解决接口不兼容的问题,而不需要修改原有代码。

适配器主要有三个角色,分别是目标接口、被适配类、适配器类。下面我以货币间转换来举例,我们现在有2个货币类,日元类和人民币类,日元类将输入的金额转为美元、人民币类则直接返回输入的金额。如果需要将日元转为人民币,那么目标接口就是人民币类(因为我们的目标是返回人民币)、被适配类是日元类(目前日元功能是转为美元,不符合我们需求,所以需要被适配)适配器类(进行具体转换的类)就是我们新建的类。具体代码如下:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 人民币类
public class Cny {

private Double rmb;

public Cny() {
}

public Cny(Double rmb) {
this.rmb = rmb;
}

public Double getRmb() {
return rmb;
}
}

// 日元类
public class Jpy {

private Double jpy;

public Jpy() {
}

public Jpy(Double jpy) {
this.jpy = jpy;
}

public Double getJpy() {
return jpy / 144.33;
}
}

// 适配器类
public class JpyToCnyAdapter extends Cny{

private Jpy jpy;

public JpyToCnyAdapter(Jpy jpy) {
this.jpy = jpy;
}

@Override
public Double getRmb() {
Double result = jpy.getJpy() * 7.05;
return result;
}
}

// 客户端
public class Main {
public static void main(String[] args) {
Double price = 100.00;
Cny cny = new Cny(price);
System.out.println("人民币类输入金额:" + price + " ,返回:" + cny.getRmb());

Jpy jpy = new Jpy(100.00);
System.out.println("日元类输入金额:" + price + " ,返回:" + jpy.getJpy());

JpyToCnyAdapter cnyAdapter = new JpyToCnyAdapter(jpy);
System.out.println("适配器类输入金额:" + price + " ,返回:" + cnyAdapter.getRmb());
}
}

// 控制台
-- 人民币类输入金额:100.0 ,返回:100.0
-- 日元类输入金额:100.0 ,返回:0.6928566479595372
-- 适配器类输入金额:100.0 ,返回:4.884639368114737

对于适配器类,我们一般记住,实现/继承的是目标接口、将被适配类引入并获取结果对其转换。

下面再来看一个例子,new Thread()接受的参数是Runnable类型,那如何支持Callable类型的任务呢,同样新建一个适配器接口即可。

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
// Callable接口的任务 不可以作为new Thread的参数
public class SimpleCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("callable执行成功");
return true;
}
}

// 适配器类,实现Runnable接口,并将callable的任务引入,在run方法内执行
public class RunnableAdapter implements Runnable{

private Callable callable;

public RunnableAdapter(Callable myCallable) {
this.callable = myCallable;
}

@Override
public void run() {
try {
callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

// 客户端代码
public class Main {
public static void main(String[] args) {
Callable callable = new SimpleCallable();
// callable不可以作为Thread的参数
// Thread thread = new Thread(callable);

// 通过适配器将其转为Runnable
RunnableAdapter adapter = new RunnableAdapter(callable);
Thread thread = new Thread(adapter);
thread.start();
}
}

我们的目标是将Callable转为Runnable,所以目标接口是Runnable,将被适配的类引入,进行逻辑重写,这里我们仅仅调用call方法即可。随后Callable类型的接口,经过适配器转换就可以变为Runnable类型的接口。

这就是适配器模式,适配器在我们的代码中有很多表现形式,这里也仅仅是冰山一角,但其核心思想是将不兼容的对象变为兼容的对象。

适用场景:第三方库的兼容、多格式文件转为统一格式、多日志集成等等

行为型

责任链(Chain of Command)

责任链可以使多个对象都有机会处理请求,我们只需要将这些对象串成一条链,那么请求就会在这条链上传递,直到被成功处理为止。责任链的一大优点就是灵活,我们可以为每个请求分配属于自己的工作链,并且可以轻松扩展。例如我们现在有吃饭、睡觉、学习三个行动,对于婴儿来说,我们只需要为其分配吃饭和睡觉,而儿童我们则可以在吃饭后追加一个学习的行为。

责任链主要由抽象处理者具体处理者客户端三个角色组成。抽象处理者负责定义处理请求的接口,并且持有对下一处理者的引用,具体处理者则是抽象处理者的子类,负责实现抽象处理者的方法并完成对应逻辑。客户端则是调用的一方,在这里我们需要完成责任链的创建并设置每个责任链的上级。

这里我们以公司内申请预算为例,如果金额在100及以下,那么交给小组审核,如果在1000及以下则交给经理,在1000以上则交给CEO处理,那么我们的责任链就可以串为小组-经理-CEO。具体代码如下:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 抽象处理者
public abstract class Handler {

protected Handler handler;

public void setNextHandler(Handler handler) {
this.handler = handler;
}

public abstract boolean request(int price);
}

// 小组具体处理者
public class GroupHandler extends Handler{

@Override
public boolean request(int price) {
if (price >= 10 && price <= 100) {
System.out.println("组审核成功!");
return true;
}
System.out.println("组审核失败,发送至下一级");
return handler.request(price);
}
}

// 经理具体处理者
public class ManagerHandler extends Handler{

@Override
public boolean request(int price) {
if (price > 100 && price <= 1000) {
System.out.println("管理审核成功");
return true;
}
System.out.println("管理审核失败,发送至下一级");
return handler.request(price);
}
}

// CEO具体处理者
public class CEOHandler extends Handler{

@Override
public boolean request(int price) {
if (price > 1000 && price < 10000) {
System.out.println("CEO审核成功");
return true;
}
System.out.println("金额过大,通过失败!");
return false;
}
}

// 客户端
public class Main {
public static void main(String[] args) {
// 创建所有责任链
GroupHandler groupHandler = new GroupHandler();
ManagerHandler managerHandler = new ManagerHandler();
CEOHandler CeoHandler = new CEOHandler();
// 设置每个责任链的上级
groupHandler.setNextHandler(managerHandler);
managerHandler.setNextHandler(CeoHandler);
System.out.println(groupHandler.request(1001));
}
}

// 控制台输出
-- 组审核失败,发送至下一级
-- 管理审核失败,发送至下一级
-- CEO审核成功
-- true

适用场景:一个请求需要被多个对象按照顺序处理时、规则校验

观察者(Observer)

观察者模式可以在一个对象发生改变时,通知所有依赖于它的对象。就跟订阅机制一样,当有更新时,所有订阅了该频道的人都会收到消息通知,这些人可以去看新消息,也可以不看,这都取决于订阅者的操作。

观察者模式由被观察者接口、被观察者实现类、观察者接口、观察者实现类和客户端组成。被观察者很好理解,如果它发生了变化,就会通知所有的观察者,被观察者一般有3个方法,添加观察者、删除观察者、消息发送,我们在具体的实现类内,需要创建一个集合,并通过这些方法维护集合和推送消息。观察者接口通常只有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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 被观察者接口
public interface Observer {
void addSubject(Subject subject);

void removeSubject(Subject subject);

void notifyObservers(String message);
}

// 观察者实现类
public class CEOObServer implements Observer{

private List<Subject> observers = new ArrayList<>();

@Override
public void addSubject(Subject subject) {
observers.add(subject);
}

@Override
public void removeSubject(Subject subject) {
observers.remove(subject);
}

@Override
public void notifyObservers(String message) {
observers.forEach(data -> data.update(message));
}
}

// 观察者接口
public interface Subject {
void update(String message);
}

// 员工观察者实现类
public class EmployeeSubject implements Subject{
@Override
public void update(String message) {
System.out.println("EmployeeSubject : " + message);
}
}

// 经理观察者实现类
public class ManagerSubject implements Subject{
@Override
public void update(String message) {
System.out.println("ManagerSubject : " + message);
}
}

// 客户端
public class Main {
public static void main(String[] args) {
// 创建一个CEO发布者和2个订阅者
CEOObServer ceoObServer = new CEOObServer();
EmployeeSubject employeeSubject = new EmployeeSubject();
ManagerSubject managerSubject = new ManagerSubject();

ceoObServer.addSubject(employeeSubject);
ceoObServer.addSubject(managerSubject);

// 发布者发布消息
ceoObServer.notifyObservers("消息通知");
System.out.println("---删除employee订阅者---");
ceoObServer.removeSubject(employeeSubject);
ceoObServer.notifyObservers("新消息通知");
}
}

// 控制台输出
-- EmployeeSubject : 消息通知
-- ManagerSubject : 消息通知
-- ---删除employee订阅者---
-- ManagerSubject : 新消息通知

适用场景:消息系统、多人协作实时编辑、对特定数据值进行监听等等

策略模式(Strategy)

策略模式可以将我们定义的一系列算法,封装到一个个独立的类中,在运行时选择不同的算法。策略模式的角色由策略接口、策略接口实现类和上下文类组成,策略接口是所有实现类的共同接口,接口实现类负责实现具体的策略,封装算法详情,上下文类提供策略对象的引用并通过接口调用返回具体策略的算法。

这里以用户观看动画来做示例,现在我们有一个动画对象,其中名字、上线年份、集数字段有值,字幕组字段没值,因为有的人喜欢A字幕组、有的人喜欢B字幕组,所以我们要让用户自己选择看哪个字幕组,用户选A我们就加载A字幕,选B我们就加载B字幕,我们通过策略模式完成这个需求,具体代码如下:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// 动画类
public class Anime {
private String name;
private String onlineYear;
private String episodes;
private String subTitle;

public Anime() {
}

public Anime(String name, String onlineYear, String episodes, String subTitle) {
this.name = name;
this.onlineYear = onlineYear;
this.episodes = episodes;
this.subTitle = subTitle;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getOnlineYear() {
return onlineYear;
}

public void setOnlineYear(String onlineYear) {
this.onlineYear = onlineYear;
}

public String getEpisodes() {
return episodes;
}

public void setEpisodes(String episodes) {
this.episodes = episodes;
}

public String getSubTitle() {
return subTitle;
}

public void setSubTitle(String subTitle) {
this.subTitle = subTitle;
}

@Override
public String toString() {
return "Anime{" +
"name='" + name + '\'' +
", onlineYear='" + onlineYear + '\'' +
", episodes='" + episodes + '\'' +
", subTitle='" + subTitle + '\'' +
'}';
}
}

// 字幕的策略接口
public interface Subtitle {
Anime getAnime(Anime anime);
}

// 北宇治字幕组实现策略接口
public class KitaujiSub implements Subtitle {
@Override
public Anime getAnime(Anime anime) {
anime.setSubTitle("北宇治字幕组");
return anime;
}
}

// 樱花字幕组实现策略接口
public class SakuraSub implements Subtitle {
@Override
public Anime getAnime(Anime anime) {
anime.setSubTitle("樱花字幕组");
return anime;
}
}

// 选择字幕的上下文类
public class SubContext {

private Subtitle subtitle;
private Anime anime;

public SubContext(Subtitle subtitle,Anime anime) {
this.subtitle = subtitle;
this.anime = anime;
}

public Anime getSubtitle() {
return subtitle.getAnime(anime);
}

}

// 客户端代码
public class Main {
public static void main(String[] args) {
// 创建2个动漫对象,字幕字段的值通过策略选择完成
Anime oshinoko = new Anime();
oshinoko.setName("推しのこ");
oshinoko.setOnlineYear("2024");
oshinoko.setEpisodes("12");

Anime makehiro = new Anime();
makehiro.setName("負けヒロインは多すぎる");
makehiro.setOnlineYear("2024");
makehiro.setEpisodes("12");

// 通过上下文对象获取加工完成的动画,参数为对应加工策略和动画对象
SubContext oshinokoSub = new SubContext(new KitaujiSub(), oshinoko);
SubContext makeiros = new SubContext(new SakuraSub(), makehiro);

System.out.println(oshinokoSub.getSubtitle());
System.out.println(makeiros.getSubtitle());
}
}

// 控制台输出
-- Anime{name='推しのこ', onlineYear='2024', episodes='12', subTitle='北宇治字幕组'}
-- Anime{name='負けヒロインは多すぎる', onlineYear='2024', episodes='12', subTitle='樱花字幕组'}

上面的例子中,算法就是不同的字幕组策略,通过传入不同的算法来使动画对象获取到不同的加工结果。策略模式同样应用在线程池创建时选择的拒绝策略上,下为四个拒绝策略的源码,可以看出策略接口为RejectedExecutionHandler,四个拒绝策略分别实现了各自的算法。

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 static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}

public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}

public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}

都说策略模式消除if-else,在选择策略的时候不还要用if-else判断选择哪个策略吗?

策略模式消除的是**行为执行中的if-else**,也就是说在执行某个动作的时候不再显示地判断使用哪种实现。举个例子就是支付场景下,我们会有很多支付方式,微信、支付宝、银行卡、信用卡等等,不使用策略模式的话,我们需要if判断用户选择的方式,然后执行这个方式所对应的代码。使用策略模式的情况下,上面的这些支付方式会被封装为一个个策略类,我们只需要通过上下文类引入对应策略即可完成,代码中没有了选择支付方式的if-else。消除的就是这部分的if-else。同时策略模式的引入也让代码具有了更好的维护性和扩展性,以后新增支付方式,我们只需要新增一个策略类即可,不需要修改原代码。这个时候可能就有人问:那么我们在选择策略的时候不还要使用if-else吗?关于这个问题,我们可以通过一个Map解决。具体如下:

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 Main {

private static final Map<String, Subtitle> SUB_MAP = new HashMap<>(){{
put("sakura", new SakuraSub());
put("kitauji", new KitaujiSub());
}};

public static void main(String[] args) {
// 创建2个动漫对象,字幕字段的值通过策略选择完成
Anime oshinoko = new Anime();
oshinoko.setName("推しのこ");
oshinoko.setOnlineYear("2024");
oshinoko.setEpisodes("12");

Anime makehiro = new Anime();
makehiro.setName("負けヒロインは多すぎる");
makehiro.setOnlineYear("2024");
makehiro.setEpisodes("12");

// 通过上下文对象获取加工完成的动画,参数为对应加工策略和动画对象
SubContext oshinokoSub = new SubContext(SUB_MAP.get("kitauji"), oshinoko);
SubContext makeiros = new SubContext(SUB_MAP.get("sakura"), makehiro);
System.out.println(oshinokoSub.getSubtitle());
System.out.println(makeiros.getSubtitle());
}
}

我们可以将所有的策略放入一个Map内,然后通过用户选择的key直接获取对应的策略,即可消除选择策略时的if-else。

策略模式与工厂方法模式有什么区别?

相信大部分人看完策略模式,会发现和工厂方法模式很像,确实这两者在结构上很像,但各自的目的又不一样。首先第一点从设计上来看,工厂方法模式属于创建型,关注的是对象的创建,而策略模式则是行为型,关注的是对算法的封装。第二点结构差异上,工厂方法包含1个接口,多个实现类和多个实现类的工厂,通过指定的工厂创建出指定的类。而策略模式包含1个策略接口、多个策略实现类和1个上下文类,通过一个上下文类选择一个策略。

适用场景:多场景支付、文件解压缩、某功能经常迭代且每次都要加入新逻辑等等