设计模式

1.设计模式七大原则

​ 编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好

  1. 代码重用性(即:相同功能的代码,不用多次编写)
  2. 可读性(即:编程规范性,便于其他程序员的阅读和理解)
  3. 可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护)
  4. 可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
  5. 使程序呈现高内聚,低耦合的特性

设计模式七大原则

设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(即:设计模式为什么这样设计的依据)

设计模式常用的七大原则有:

  • 单一职责原则
  • 接口隔离原则
  • 依赖倒转(倒置)原则
  • 里氏替换原则
  • 开闭原则
  • 迪米特法则
  • 合成复用原则

单一职责原则

对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责⒉执行错误,所以需要将类A的粒度分解为A1,A2

代码演示:

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
package principle;

/***
* @author dep
* @version 1.0
* @date 2023-03-13 22:31
*/
public class SingleResponsibility {
public static void main(String[] args) {
// 方案一 违反单一职责原则
Vehicle vehicle = new Vehicle();
vehicle.run("汽车");
vehicle.run("轮船");
vehicle.run("飞机");

// 方案二 遵循单一职责原则
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("汽车");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飞机");
WatterVehicle watterVehicle = new WatterVehicle();
watterVehicle.run("轮船");

// 方案三
Vehicle2 vehicle2 = new Vehicle2();
vehicle2.runAir("飞机");
vehicle2.runRoad("公路");
vehicle2.runWatter("轮船");
}


}
// 交通工具类
// 方式1,run方法违反了单一职责原则
// 解决方案,根据交通工具运行方法不同,分解成不同的类
class Vehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在公路上运行...");
}
}
// 方案2 遵循了单一职责原则
// 但是这样做的改动很大,即 将类分解,同时修改客户端
// 改进: 直接修改Vehicle类,改动的代码会比较少
class RoadVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在公路上运行...");
}
}

class AirVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在天空上运行...");
}
}

class WatterVehicle {
public void run(String vehicle) {
System.out.println(vehicle + "在水上运行...");
}
}

// 方案三
// 这种修改方法没有对原来的类做大的修改,只是增加方法
// 这李虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责原则
class Vehicle2 {
public void runRoad(String vehicle) {
System.out.println(vehicle + "在公路上运行...");
}
public void runAir(String vehicle) {
System.out.println(vehicle + "在天空上运行...");
}
public void runWatter(String vehicle) {
System.out.println(vehicle + "在水上运行...");
}
}

单一职责原则注意事项和细节:

  1. 降低类的复杂度,一个类只负责一项职责。
  2. 提高类的可读性,可维护性
  3. 降低变更引起的风险
  4. 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中
    方法数量足够少,可以在方法级别保持单一职责原则

接口隔离原则

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上。

image-20230315213857235

类A通过接口Interfacel依赖类B,类C通过接口Interface1依赖类D,如果接口Interfacel对于类A和类C
来说不是最小接口,那么类B和类D必须去实现他们不需要的方法。
4)按隔离原则应当这样处理:
将接口Interface1拆分为独立的几个接口(这里我们拆分成3个接口),类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则

传统方式实现:

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
package principle;

/***
* @author dep
* @version 1.0
* @date 2023-03-15 21:32
* 接口隔离原则
*/
public class InterfaceSegregation {
public static void main(String[] args) {
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D());
c.depend4(new D());
c.depend5(new D());
}
}

// 传统方法
// 接口1
interface Interface1 {
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
class B implements Interface1 {

@Override
public void operation1() {
System.out.println("B 实现了 operation1");
}

@Override
public void operation2() {
System.out.println("B 实现了 operation2");
}

@Override
public void operation3() {
System.out.println("B 实现了 operation3");
}

@Override
public void operation4() {
System.out.println("B 实现了 operation4");
}

@Override
public void operation5() {
System.out.println("B 实现了 operation5");
}
}
class D implements Interface1 {

@Override
public void operation1() {
System.out.println("D 实现了 operation1");
}

@Override
public void operation2() {
System.out.println("D 实现了 operation2");
}

@Override
public void operation3() {
System.out.println("D 实现了 operation3");
}

@Override
public void operation4() {
System.out.println("D 实现了 operation4");
}

@Override
public void operation5() {
System.out.println("D 实现了 operation5");
}
}
class A { // A类通过接口Interface1依赖(使用)B类,但是只会用到1,2,3方法
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface1 i) {
i.operation2();
}
public void depend3(Interface1 i) {
i.operation3();
}
}
class C { // C类通过接口Interface1 依赖(使用) D类,但是只会用到1,4,5方法
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface1 i) {
i.operation4();
}
public void depend5(Interface1 i) {
i.operation5();
}
}

Il)类A通过接口 Interfacel依赖类B,类C通过接口 Interfacel依赖类D,如果接口Interfacel对于类A和类C
来说不是最小接口,那么类B和类D必须去实现他们不需要的方法
2)将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口
隔离原则
3)接口 Interface1中出现的方法,根据实际情况拆分为三个接口

image-20230315215116869

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
package principle;

/***
* @author dep
* @version 1.0
* @date 2023-03-15 21:32
* 接口隔离原则
*/
public class InterfaceSegregation {
public static void main(String[] args) {
newA newA = new newA();
newA.depend1(new newB());
newA.depend2(new newB());
newA.depend3(new newB());
newC newC = new newC();
newC.depend1(new newD());
newC.depend4(new newD());
newC.depend5(new newD());
}
}
// 接口隔离原则
interface InterfaceNew1 {
void operation1();
}
interface InterfaceNew2 {
void operation2();
void operation3();
}
interface InterfaceNew3 {
void operation4();
void operation5();
}
class newB implements InterfaceNew1, InterfaceNew2 {

@Override
public void operation1() {
System.out.println("B实现了InterfaceNew1的operation1方法");
}

@Override
public void operation2() {
System.out.println("B实现了InterfaceNew2的operation2方法");
}

@Override
public void operation3() {
System.out.println("B实现了InterfaceNew2的operation3方法");
}
}
class newD implements InterfaceNew1, InterfaceNew3 {

@Override
public void operation1() {
System.out.println("D实现了InterfaceNew1的operation1方法");
}

@Override
public void operation4() {
System.out.println("D实现了InterfaceNew3的operation4方法");
}

@Override
public void operation5() {
System.out.println("D实现了InterfaceNew3的operation5方法");
}
}
class newA { // A类通过接口Interface1依赖(使用)B类,但是只会用到1,2,3方法
public void depend1(InterfaceNew1 i) {
i.operation1();
}
public void depend2(InterfaceNew2 i) {
i.operation2();
}
public void depend3(InterfaceNew2 i) {
i.operation3();
}
}
class newC { // C类通过接口Interface1 依赖(使用) D类,但是只会用到1,4,5方法
public void depend1(InterfaceNew1 i) {
i.operation1();
}
public void depend4(InterfaceNew3 i) {
i.operation4();
}
public void depend5(InterfaceNew3 i) {
i.operation5();
}
}

依赖倒转原则

依赖倒转原则(Dependence Inversion Principle)是指:

  1. 高层模块不应该依赖低层模块,二者都应该依赖其抽象
  2. 抽象不应该依赖细节,细节应该依赖抽象
  3. 依赖倒转(倒置)的中心思想是面向接口编程
  4. 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架
    构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类
  5. 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

// 传统方法出现的问题

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
package principle;

/***
* @author dep
* @version 1.0
* @date 2023-03-15 22:23
*/
public class DependencyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}

class Email {
public String getInfo() {
return "电子邮件信息:hello,world;";
}
}
// 如果我们获取的对象是微信,短信等等,则新增类,同时Perons也要增加相应的接收方法
// 解决思路:引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver 发生依赖
// 因为 Email, WeiXin等等属于接收的范围,他们各自实现IReceiver 接口就ok,这样我们就符号依赖倒转原则
class Person {
public void receive(Email email) {
System.out.println(email.getInfo());
}
}

// 改进,使用接口实现依赖倒转

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
package principle;

/***
* @author dep
* @version 1.0
* @date 2023-03-15 22:23
*/
public class DependencyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
person.receive(new WeiXin());
}
}

// 利用依赖倒转原则
interface IReceiver {
String getInfo();
}
class Email implements IReceiver {

@Override
public String getInfo() {
return "电子邮件信息:hello,world;";
}
}
class WeiXin implements IReceiver {

@Override
public String getInfo() {
return"微信信息:hello,world;";
}
}
class Person {
public void receive(IReceiver iReceiver) {
System.out.println(iReceiver.getInfo());
}
}

依赖关系传递的三种方式

接口传递实现依赖
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 class DependencyInversionMethod {
public static void main(String[] args) {
ChangHong changHong = new ChangHong();
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.open(changHong);
}
}

// 通过接口传递实现依赖
// 开关的接口
interface IOpenAndClose {
public void open(ITV tv); // 抽象方法;接收接口
}
interface ITV { // ITV 接口
public void play();
}
class ChangHong implements ITV {
@Override
public void play() {
System.out.println("长虹电视机,打开");
}
}
// 实现接口
class OpenAndClose implements IOpenAndClose {
@Override
public void open(ITV tv) {
tv.play();;
}
}
构造方法依赖传递
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 DependencyInversionMethod {
public static void main(String[] args) {
// ChangHong changHong = new ChangHong();
// OpenAndClose openAndClose = new OpenAndClose();
// openAndClose.open(changHong);

ChangHong changHong = new ChangHong();
OpenAndClose openAndClose = new OpenAndClose(changHong);
openAndClose.open();
}
}
// 方式2,通过构造方法传递依赖
interface IOpenAndClose {
public void open(); // 抽象方法
}
interface ITV { // ITV接口
public void play();
}
class OpenAndClose implements IOpenAndClose {
public ITV tv; // 成员

public OpenAndClose(ITV tv) { // 构造器
this.tv = tv;
}

@Override
public void open() {
this.tv.play();
}
}
class ChangHong implements ITV {
@Override
public void play() {
System.out.println("长虹电视机,打开");
}
}
通过setter方法传递
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 DependencyInversionMethod {
public static void main(String[] args) {
ChangHong changHong = new ChangHong();
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.setTv(changHong);
openAndClose.open();
}
}
// 方式三:通过setter方法传递
interface IOpenAndClose {
public void open(); // 抽象方法

public void setTv(ITV tv);
}
interface ITV { // ITV接口
public void play();
}
class OpenAndClose implements IOpenAndClose {

public ITV tv;

@Override
public void open() {
this.tv.play();
}

@Override
public void setTv(ITV tv) {
this.tv = tv;
}
}
class ChangHong implements ITV {
@Override
public void play() {
System.out.println("长虹电视机,打开");
}
}

依赖倒转原则的注意事项和细节

  1. 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好.
  2. 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展
    和优化
  3. 继承时遵循里氏替换原则

里氏替换原则

1)继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有
的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。

2)继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,
增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障
3)问题提出:在编程中,如何正确的使用继承?→>里氏替换原则

在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。

// 原有存在的问题

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 class RichterSubstitution {
public static void main(String[] args) {
AA a = new AA();
System.out.println(a.func1(3, 2));

BB bb = new BB();
System.out.println(bb.func1(3,2)); // 本意是求3-2,但是子类BB无意改写了
System.out.println(bb.fun2(3,2));

}

}
// Aa类
class AA {
// 返回两个数的差
public int func1(int num1, int num2) {
return num1 - num2;
}
}
// BB类继承了AA
class BB extends AA {
// 这里,重写AA类的方法,可能是无意识的
public int func1(int a, int b) {
return a+b;
}
public int fun2(int a, int b) {
return func1(a,b) + 9;
}
}

解决方案

  1. 我们发现原来运行正常的相减功能发生了错误。原因就是类B无意中重写了父类的方法,造成原有功能出现错
    误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候。

    2.通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替.

image-20230316213651815

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
package principle;

public class RichterSubstitution {
public static void main(String[] args) {
AA a = new AA();
System.out.println(a.func1(3, 2));

BB bb = new BB();
System.out.println(bb.func1(3,2));
System.out.println(bb.fun2(3,2));
System.out.println(bb.fun3(3,2));

}

}

// 里氏替换原则
// 创建一个更加基础的基类
class Base {
// 把更加基础的方法和成员写到Base类
}
// Aa类继承Base类
class AA extends Base {
// 返回两个数的差
public int func1(int num1, int num2) {
return num1 - num2;
}
}
// BB类继承了AA
class BB extends Base {

// 如果B需要使用A类的方法,使用组合关系
private AA a = new AA();

public int func1(int a, int b) {
return a+b;
}
public int fun2(int a, int b) {
return func1(a,b) + 9;
}
// 仍然想使用A的方法
public int fun3(int a, int b) {
return this.a.func1(a,b);
}
}

开闭原则

  1. 开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则
  2. 一个软件实体如类,模块和函数应该**对扩展开放(对提供方),对修改关闭(对使用方)**。用抽象构建框架,用实
    现扩展细节。
  3. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

// 传统方式

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
package principle;

public class OpenClosePrinciple {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}


// 传统方法缺点,违反设计的ocp的原则,即对扩展开放(提供方),对修改关闭(使用方)。
// 比如我们这要新增加一个图像类型 三角形,需要修改的地方比较多
// 这是一个用于绘图的类
class GraphicEditor {
// 接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape s) {
if (s.m_type == 1) {
drawRectangle(s);
} else if (s.m_type == 2) {
drawCircle(s);
} else if (s.m_type == 3) {
drawTriangle(s);
}
}

// 绘制矩形
public void drawRectangle (Shape r){
System.out.println("绘制矩形");
}

// 绘制圆形
public void drawCircle(Shape r) {
System.out.println("绘制圆形");
}

// 绘制三角形
public void drawTriangle(Shape r) {
System.out.println("绘制三角形");
}
}

// Shape类,基类
class Shape {
int m_type;
}

class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}
}
class Circle extends Shape {
Circle() {
super.m_type = 2;
}
}
class Triangle extends Shape {
Triangle() {
super.m_type = 3;
}
}
  1. 优点是比较好理解,简单易操作。
  2. 缺点是违反了设计模式的ocp原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码.
  3. 比如我们这时要新增加一个图形种类三角形,我们需要做如下修改,修改的地方较多

改进:

思路:把创建 Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现 draw方法即可,使用方的代码就不需要修→>满足了开闭原则

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
package principle;

import javafx.scene.shape.Circle;

/***
* @author dep
* @version 1.0
* @date 2023-03-16 22:19
* 开闭原则
*/
public class OpenClosePrinciple {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle1());
graphicEditor.drawShape(new Triangle());
}
}

// 开闭原则
class GraphicEditor {
// 接收shape方法,调用draw方法
public void drawShape(Shape s) {
s.draw();
}
}
abstract class Shape {
int m_type;
public abstract void draw(); // 抽象方法
}
class Rectangle extends Shape {
Rectangle() {
super.m_type = 1;
}

@Override
public void draw() {
System.out.println("绘制矩形");
}
}
class Circle1 extends Shape {
Circle1() {
super.m_type = 2;
}

@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class Triangle extends Shape {
Triangle() {
super.m_type = 3;
}

@Override
public void draw() {
System.out.println("绘制三角形");
}
}

迪米特法则

  1. 一个对象应该对其他对象保持最少的了解
  2. 类与类关系越密切,耦合度越大
  3. 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息
  4. 迪米特法则还有个更简单的定义:只与直接的朋友通信
  5. 直接的朋友:每个对象都会其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间
    是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

有一个学校,下属有各个学院和总部,现要求打印出学校总部员工ID和学院员工的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
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
package principle;

import java.util.ArrayList;
import java.util.List;

/***
* @author dep
* @version 1.0
* @date 2023-03-17 16:06
* 迪米特原则
*/
public class DemeterPrinciple {
public static void main(String[] args) {
//创建了一个SchoolManager对象
SchoolManager schoolManager = new SchoolManager();
//输出学院的员工id 和﹐学校总部的员工信息
schoolManager.printAllEmployee(new CollegeManager());

}
}

//学校总部员工类
class Employee {
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
// 学院的员工类
class CollegeEmployee {
private String id;

public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
}

// 管理学院员工的管理类
class CollegeManager {
// 返回学校的所有员工
public List<CollegeEmployee> getAllEmployee() {
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) {
// 这里我们增加了10个员工到list
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工 id=" + i);
list.add(emp);
}
return list;
}

// ADD 新增的方法
// 输出学院员工信息
public void printEmployee() {
// 获取学院员工
List<CollegeEmployee> list1 = this.getAllEmployee();
System.out.println("------------学院员工------------");
for (CollegeEmployee e : list1){
System.out.println(e.getId());
}
}

}
// 学校管理类
class SchoolManager {
//返回学校总部的员工
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<Employee>();
for (int i=0; i<5; i++){
//这里我们增加了5个员工到list
Employee emp= new Employee();
emp.setId("学校总部员工 id="+ i);list.add(emp);
}
return list;
}
// 该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub) {

// 分析问题,这里的CollegeEmployee不是SchoolManager的直接朋友
// //2.CollegeEmployee是以局部变量方式出现在SchoolManager
// 违反了迪米特原则

// //获取到学院员工
// List<CollegeEmployee> list1 = sub.getAllEmployee();
// System.out.println("------------学院员工------------");
// for (CollegeEmployee e : list1){
// System.out.println(e.getId());
// }

// ADD 改进的方法
sub.printEmployee();

//获取到学校总部员工
List<Employee> list2 =this.getAllEmployee();
System.out.println(" ------------学校总部员工------------");
for (Employee e : list2){
System.out.println(e.getId());
}
}
}
  1. 迪米特法则的核心是降低类之间的耦合
  2. 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系

合成复用原则

原则是尽量使用合成/聚合的方式,而不是使用继承

image-20230317172151584

设计原则核心思想

  1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
  2. 针对接口编程,而不是针对实现编程。
  3. 为了交互对象之间的松耦合设计而努力

UML类图

  1. UML——Unified modeling language UML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用
    于帮助软件开发人员进行思考和记录思路的结果
  2. UML本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他
    们之间的关系,比如类、接口、实现、泛化、依赖、组合、聚合等,如右图:

image-20230318174041821

UML图

  1. 用例图(use case)
  2. 静态结构图:类图、对象图、包图、组件图、部署图
  3. 动态行为图:交互图(时序图与协作图)、状态图、活动图

类图是描述类与类之间关系的

UML类图

  1. 用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系。
  2. 类之间的关系**:依赖、泛化(继承)、实现、关联、聚合与组合。**
1
2
3
4
5
6
7
8
9
10
11
12
public class Person {
int id;
String name;

public String getName() {
return name;
}

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

image-20230319162911801

类图—依赖关系(Dependence)

只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编绎都通过不了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package UML.dependency;

import UML.Person;

/***
* @author dep
* @version 1.0
* @date 2023-03-19 16:40
*/
public class PersonServiceBean {
private PersonDao personDao;//类

public void save(Person person){}

public IDCard getIDCard(Integer personid){
return null;
}
public void modify() {
Department department = new Department();
}
}

image-20230319164427937

  1. 类中用到了对方
  2. 如果是类的成员属性
  3. 如果是方法的返回类型
  4. 是方法接收的参数类型
  5. 方法中使用到

类图—泛化关系(generalization)

泛化关系实际上就是继承关系,他是依赖关系的特例

1
2
3
4
public abstract class DaoSupport {
public void save(Object entity){}
public void delete(Object id){}
}
1
2
3
4
5
6
7
8
public class PersonServiceBean extends DaoSupport{
public void save(Object entity){
System.out.println("save");
}
public void delete(Object id){
System.out.println("delete");
}
}

image-20230319165040691

  • 泛化关系实际上就是继承关系
  • 如果A类继承了B类,我们就说A和B存在泛化关系

类图—实现关系(Implementation)

实现关系实际上就是A类实现B接口,他是依赖关系的特例

1
2
3
public interface PersonService {
public void delete(int id);
}
1
2
3
4
5
6
public class PersonServiceImpl implements PersonService{
@Override
public void delete(int id) {
System.out.println("删除");
}
}

image-20230319165429872

类图—关联关系(Association)

image-20230319170748354

类图—聚合关系(Aggregation)

聚合关系(Aggregation)表示的是整体和部分的关系,整体与部分可以分开。聚合关系是关联关系的特例,所以他具有关联的导航性与多重性。

如:一台电脑由键盘(keyboard)、显示器(monitor),鼠标等组成;组成电脑的各个配件是可以从电脑上分离出来的,使用带空心菱形的实线来表示:

image-20230319170858300

整体和部分是分开的,聚合关系

类图─组合关系(Composition)

组合关系:也是整体与部分的关系,但是整体与部分不可以分开

1
2
3
4
public class Computer {
Mouse mouse = new Mouse(); // 整体与部分是可以不分开的,是组合关系
Monitor monitor = new Monitor(); // 整体与部分是可以不分开的,是组合关系
}

image-20230319171806371

1
2
3
4
public class Person{
private IDCard card; // 聚合,整体和部分可以分开
private Head head = new Head(); // 组合,整体和部分不可以分开
}

image-20230319171905515

如何使用IDEA画UML图

 安装步骤为:File -> Settings -> Plugins 搜索 PlantUML ,找到 PlantUML integration 并安装。

image-20230319155329001

安装Graphviz

IDEA 安装 PlantUML 插件之后发现光有插件还不能渲染类图,还需要 Graphviz 的支持。

Graphviz安装

 安装包下载地址:https://graphviz.org/download/。下载完成之后解压

image-20230319155530419

配置环境变量

image-20230319155745444

配置完成之后打开 cmd 输入:dot -version,如果版本号打印成功,说明环境配置完成。

image-20230319155857420

成功之后重新启动 IDEA 即可创建 PlantUML File 了。

image-20230319160113382

不好用

总结

继承(extends)

箭头子类指向父类

abstract 抽象类,abstract 抽象方法,斜体方式实现

static静态字段, static静态方法, 带有下划线

接口与实现(implements)(泛化generalization)

空心箭头

箭头从实现类指向接口

image-20230320223607282

聚合

image-20230320223815057

可见性(访问控制)

  • “+”表示public方法和字段,可以从类外部访问这些方法和字段。
  • “-”表示 private方法和字段,无法从类外部访问这些方法和字段。
  • “#”表示protect方法和字段,能够访问这些方法和字段的只能是该类自身、该类的子类以及同一包中的类。
  • “~”表示只有同一包中的类才能访问的方法和字段。

image-20230320224201126

类的关联

image-20230320224357812

时序图

设计模式概述

  1. 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式〈Design pattern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
  2. 设计模式的本质提高软件的维护性,通用性和扩展性,并降低软件的复杂度。

设计模式类型

  1. 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
  2. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
  3. 行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、
    备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。

创建型模式

单例模式(⭐⭐⭐⭐)

​ 所谓类的单例设计模式,就是**采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,**并且该类只提供一个取得其对象实例的方法(静态方法)。

​ 比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这是就会使用到单例模式.

单例设计模式八种方式

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

饿汉式(静态常量)

步骤如下:

  1. 构造器私有化(防止new )
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法。getInstance
  4. 代码实现
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 class SingletonTest01 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance == instance1);
System.out.println("instance:" + instance.hashCode());
System.out.println("instance1:" + instance1.hashCode());
}
}

// 饿汉式(静态变量)
// 1.构造器私有化(防止new)
// 2.类的内部创建
// 3.向外暴露一个静态的公共方法
// 4.代码实现

class Singleton {
// 1.构造器私有化,外部不能 new
private Singleton() {

}
// 2.在本类内部创建实例对象
private final static Singleton singleton = new Singleton();

// 3.向外暴露一个静态的公共方法
public static Singleton getInstance() {
return singleton;
}
}

优缺点:

  1. 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
  2. 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading 的效果。如果从始至终从未使用过这个实例,则
    会造成内存的浪费
  3. 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getlnstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading 的效果
  4. 结论:这种单例模式可用,可能造成内存浪费

饿汉式(静态代码块)

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
package SingletonPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-19 20:20
*/
public class SingletonTest02 {
public static void main(String[] args) {
Singleton2 instance = Singleton2.getInstance();
Singleton2 instance1 = Singleton2.getInstance();
System.out.println(instance == instance1);
System.out.println("instance:" + instance.hashCode());
System.out.println("instance1:" + instance1.hashCode());
}
}

// 饿汉式(静态代码块)
// 1.构造器私有化(防止new)
// 2.类的内部创建
// 3.向外暴露一个静态的公共方法
// 4.代码实现

class Singleton2 {
// 1.构造器私有化,外部不能 new
private Singleton2() {

}
// 2.在本类内部创建实例对象
private final static Singleton2 singleton;

static {
singleton = new Singleton2();
}

// 3.向外暴露一个静态的公共方法
public static Singleton2 getInstance() {
return singleton;
}
}

  1. 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
  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 SingletonTest03 {
public static void main(String[] args) {
Singleton3 instance = Singleton3.getInstance();
Singleton3 instance1 = Singleton3.getInstance();
System.out.println(instance == instance1);
System.out.println("instance:" + instance.hashCode());
System.out.println("instance1:" + instance1.hashCode());
}
}


class Singleton3 {
private Singleton3 (){}

private static Singleton3 instance;

public static Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
  1. 起到了Lazy Loading 的效果,但是只能在单线程下使用。
  2. 如果在多线程下,一个线程进入了if(singleton ==null)判断语句块,还未来得及往下执行,另一个线程也通过
    了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
  3. 结论:在实际开发中,不要使用这种方式.

懒汉式(线程安全,同步方法)(不推荐)

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 SingletonTest04 {
public static void main(String[] args) {
Singleton4 instance = Singleton4.getInstance();
Singleton4 instance1 = Singleton4.getInstance();
System.out.println(instance == instance1);
System.out.println("instance:" + instance.hashCode());
System.out.println("instance1:" + instance1.hashCode());
}
}

// 懒汉式(线程安全)
class Singleton4 {
private Singleton4 (){}

private static Singleton4 instance;

// 加入同步处理代码,解决线程安全问题
public static synchronized Singleton4 getInstance() {
if (instance == null) {
instance = new Singleton4();
}
return instance;
}
}
  1. 解决了线程安全问题
  2. 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低
  3. 结论:在实际开发中,不推荐使用这种方式

懒汉式(线程安全,同步代码块,不推荐)

image-20230319211703661

  1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块
  2. 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
  3. 结论:在实际开发中,不能使用这种方式

双重检查(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 双重检查
class Singleton6 {
private Singleton6 (){}

private static Singleton6 instance;

// 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
// 同时保证了效率,推荐使用
public static synchronized Singleton6 getInstance() {
if (instance == null) {
synchronized (Singleton6.class) {
instance = new Singleton6();
}
}
return instance;
}
}
  1. Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (singleton == null)检查,这
    样就可以保证线程安全了。
  2. 这样,实例化代码只用执行一次,后面再次访问时,判断 if (singleton ==null),直接return实例化对象,也避
    免的反复进行方法同步.
  3. 线程安全;延迟加载;效率较高
  4. 结论:在实际开发中,推荐使用这种单例设计模式

静态内部类(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 静态内部类
class Singleton7 {
private Singleton7 (){}

// 写一个静态内部类该类中有一个静态属性 Singleton7
private static class SingletonClass {
private static final Singleton7 instance = new Singleton7();
}

public static synchronized Singleton7 getInstance() {
return SingletonClass.instance;
}
}
  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  2. 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才
    会装载SingletonInstance类,从而完成Singleton的实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
  4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
  5. 结论:推荐使用.

枚举(推荐)

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
package SingletonPattern;

import com.sun.org.apache.bcel.internal.generic.INSTANCEOF;

import java.util.EnumMap;

/***
* @author dep
* @version 1.0
* @date 2023-03-19 21:00
*/
public class SingletonTest08 {
public static void main(String[] args) {
Singleton8 instance = Singleton8.INSTANCE;
Singleton8 instance1 = Singleton8.INSTANCE;
System.out.println(instance == instance1);
instance1.sayok();
System.out.println("instance:" + instance.hashCode());
System.out.println("instance1:" + instance1.hashCode());
}
}

// 枚举
enum Singleton8{
INSTANCE;
public void sayok() {
System.out.println("ok");
}
}
  1. 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建
    新的对象。
  2. 这种方式是 Effective Java作者Josh Bloch提倡的方式
  3. 结论:推荐使用

单例模式在JDK应用的源码分析

image-20230320211930208

饿汉式(静态常量)

总结

  1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
  3. 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session 工厂等)

工厂模式

简单工厂模式(⭐⭐⭐)

image-20230322192614432

1
2
3
4
5
6
7
8
//相当于一个客户端,发出订购
public class PizzaStore {

public static void main(String[] args) {
//使用简单工厂模式
new OrderPizza(new SimpleFactory());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// 简单工厂类
public class SimpleFactory {

// 返回对应的Pizza
public Pizza createPizza(String orderType) {
Pizza pizza = null;
System.out.println("简单工厂模式");
if (orderType.equals("greek")) {
pizza = new GreekPizza();
pizza.setName("希腊披萨");
} else if (orderType.equals("cheese")) {
pizza = new CheesePizza();
pizza.setName(" 奶酪披萨 ");
} else if (orderType.equals("pepper")) {
pizza = new PepperPizza();
pizza.setName("胡椒披萨");
}

return pizza;
}
}
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
public class OrderPizza {
//定义一个简单工厂对象
SimpleFactory simpleFactory;
Pizza pizza = null;

//构造器
public OrderPizza(SimpleFactory simpleFactory) {
setFactory(simpleFactory);
}

public void setFactory(SimpleFactory simpleFactory) {
String orderType = ""; //用户输入的

this.simpleFactory = simpleFactory; //设置简单工厂对象

do {
orderType = getType();
pizza = this.simpleFactory.createPizza(orderType);

//输出pizza
if(pizza != null) { //订购成功
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println(" 订购披萨失败 ");
break;
}
}while(true);
}

// 写一个方法,可以获取客户希望订购的披萨种类
private String getType() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
System.out.println("input pizza 种类:");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}

工厂模式在JDK-Calendar应用的源码分析

1
2
3
4
5
6
7
8
9
10
11
12
public class factory {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
// 注意月份下标从0开始,所以取月份要+1
System.out.println("年:"+calendar.get(Calendar.YEAR));
System.out.println("月:"+(calendar.get(Calendar.MONTH)+ 1));
System.out.println("日:"+calendar.get(Calendar.DAY_OF_MONTH));
System.out.println("时:"+calendar.get(Calendar.HOUR_OF_DAY));
System.out.println("分:"+calendar.get(Calendar.MINUTE));
System.out.println("秒:"+calendar.get(Calendar.SECOND));
}
}
1
2
3
4
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
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
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{
CalendarProvider provider =
LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
try {
return provider.getInstance(zone, aLocale);
} catch (IllegalArgumentException iae) {
// fall back to the default instantiation
}
}

Calendar cal = null;

if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
// If no known calendar type is explicitly specified,
// perform the traditional way to create a Calendar:
// create a BuddhistCalendar for th_TH locale,
// a JapaneseImperialCalendar for ja_JP_JP locale, or
// a GregorianCalendar for any other locales.
// NOTE: The language, country and variant strings are interned.
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}

工厂方法模式(⭐⭐⭐⭐⭐)

抽象工厂模式(⭐⭐⭐⭐⭐)

原型模式(⭐⭐⭐)

  • 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  • 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()
  • 形象的理解:孙大圣拔出猴毛,变出其它孙大圣

image-20230323102201516

  1. Prototype:原型类,声明一个克隆自己的接口
  2. ConcretePrototype:具体的原型类,实现一个克隆自己的操作
  3. Client:让一个原型对象克隆自己,从而创建一个新的对象(属性一样)

传统模式

image-20230323104203721

传统模式优缺点

  1. 优点是比较好理解,简单易操作。
  2. 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
  3. 总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活
  4. 改进的思路分析
    思路: Java中 0bject类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力→原型模式

原型模式代码实现

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
package PrototypePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 10:31
*/
public class Sheep implements Cloneable {
private String name;
private int age;
private String color;

public String getName() {
return name;
}

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

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}

public Sheep(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}

@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}';
}

// 克隆该实例,使用默认的clone方法完成
@Override
protected Object clone() throws CloneNotSupportedException {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
}catch (Exception e) {
System.out.println(e.getMessage());
}
return sheep;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package PrototypePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 10:36
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Sheep sheep = new Sheep("tom", 1 , "白色");

Sheep sheep1 = (Sheep) sheep.clone();
Sheep sheep2 = (Sheep) sheep.clone();
Sheep sheep3 = (Sheep) sheep.clone();
Sheep sheep4 = (Sheep) sheep.clone();
System.out.println("sheep1:" + sheep1);
System.out.println("sheep2:" + sheep2);
System.out.println("sheep3:" + sheep3);
System.out.println("sheep4:" + sheep4);
System.out.println(sheep1.hashCode());
System.out.println(sheep.hashCode());
}
}

原型模式在Spring框架中源码分析

  • Spring中原型bean的创建,就是原型模式的应用
  • 代码分析

pom.xml导入依赖

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>

resources/beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">


<!-- 这里我们的 scope="prototype" 即 原型模式来创建 -->
<bean id="id01" class="Spring.bean.Monster"
scope="prototype"/>


</beans>

spring/bean/Monster

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
package Spring.bean;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 11:03
*/
public class Monster {

private Integer id = 10 ;
private String nickname = "牛魔王";
private String skill = "芭蕉扇";
public Monster() {

System.out.println("monster 创建..");
}
public Monster(Integer id, String nickname, String skill) {
//System.out.println("Integer id, String nickname, String skill被调用");
this.id = id;
this.nickname = nickname;
this.skill = skill;
}

public Monster( String nickname, String skill,Integer id) {

this.id = id;
this.nickname = nickname;
this.skill = skill;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
@Override
public String toString() {
return "Monster [id=" + id + ", nickname=" + nickname + ", skill="
+ skill + "]";
}
}

prototype

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
package Spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 10:56
*/
public class prototype {
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
// 获取monster[通过id获取monster]
Object bean = applicationContext.getBean("id01");
System.out.println("bean" + bean); // 输出 "牛魔王" .....

Object bean2 = applicationContext.getBean("id01");

System.out.println("bean2" + bean2); //输出 "牛魔王" .....

System.out.println(bean == bean2); // false
}
}

源码

image-20230323111555775

1
2
3
4
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}

image-20230323111912107

浅拷贝和深拷贝

  1. 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制–份给新的对象。
  2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在-一个对象中修改该成员变量会影响到另一个对象的该成员变量值
  3. 前面我们克隆羊就是浅拷贝.
  4. 浅拷贝是使用默认的clone( )方法来实现
    sheep = (Sheep) super.clone();

image-20230323121958844

深拷贝

  1. 复制对象的所有基本数据类型的成员变量值
  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
  3. 深拷贝实现方式1:重写clone方法来实现深拷贝
  4. 深拷 贝实现方式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
package PrototypePattern.deepclone;

import java.io.Serializable;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 12:37
*/
public class DeepCloneableTarget implements Serializable, Cloneable {
private static final long serialVersionUID= 1L;
private String cloneName;
private String cloneClass;

public String getCloneName() {
return cloneName;
}

public void setCloneName(String cloneName) {
this.cloneName = cloneName;
}

public DeepCloneableTarget(String cloneName, String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}

public String getCloneClass() {
return cloneClass;
}

public void setCloneClass(String cloneClass) {
this.cloneClass = cloneClass;
}

// 该类的属性都是String,使用默认的clone方法就行
@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
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
package PrototypePattern.deepclone;

import java.io.*;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 12:40
*/
public class DeepProtoType implements Serializable, Cloneable {
public String name;
public DeepCloneableTarget deepCloneableTarget; // 引用类型

public DeepProtoType() {
}

// 深拷贝,方法一, 使用clone方法
// @Override
// protected Object clone() throws CloneNotSupportedException {
// Object res = null;
// // 这里完成对基本数据类型的拷贝(也会拷贝上引用类型,不过拷贝的是栈内存中的地址)
// res = super.clone();
// // 对引用类型单独处理(将object转为DeepProtoType,也就是浅拷贝克隆后的对象)
// DeepProtoType deepProtoType = (DeepProtoType)res;
// // 将对象里面的引用类型再拷贝一份
// deepProtoType.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();
// return deepProtoType;
// }

// 深拷贝 通过对象的序列化实现(推荐)
@Override
protected Object clone() throws CloneNotSupportedException {
// 创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;

try {
// 序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); // this指向这个函数的调用者,,当前这个对象以对象流的方式输出

// 反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType deepProtoType = (DeepProtoType)ois.readObject();

return deepProtoType;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}

}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package PrototypePattern.deepclone;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 12:49
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
DeepProtoType p = new DeepProtoType();
p.name = "宋江";
p.deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");

DeepProtoType p2 = (DeepProtoType) p.clone();
System.out.println("p.name= =" + p.name + "p.deepCloneableTarget= " + p.deepCloneableTarget.hashCode());
System.out.println( "p2.name=" + p.name + "p2 deepCloneableTarget=" + p2 .deepCloneableTarget.hashCode());

}
}

总结

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  2. 不用 重新初始化对象,而是动态地获得对象运行时的状态
  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码
  4. 在实现深克隆的时候可能需要比较复杂的代码
  5. 缺点:需要为每一个类配备-一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则,这点请同学们注意.

建造者模式(⭐⭐)

传统方式解决盖房需求

image-20230323143651792

  1. 优点是比较好理解,简单易操作。
  2. 设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好.也就是说,这种设计方案,把产品(即:房子)和创建产品的过程(即:建房子流程)封装在一起,耦合性增强了。
  3. 解决方案:将产品和产品建造过程解耦=>建造者模式

建造者模式介绍

建造者模式:将一个对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

  1. 建造者模式**(Builder Pattern)又叫生成器模式,是-种对象构建模式**。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
  2. 建造者模式是–步一步创建-一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

建造模式的四个角色

  • Product (产品角色) :一个具体的产品对象。
  • Builder (抽象建造者) :创建 一个Product对象的各个部件指定的接口/抽象类。
  • ConcreteBuilder (具体建造者) :实现接口,构建和装配各个部件。
  • Director (指挥者) :构建一个使用Builder接口的对象。它主要是用于创建-一个复杂的对象。它主要有两个作用,- -是: 隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

建造者模式原理类图

image-20230323144333940

代码实现

image-20230323144632398

House(产品)

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
package builderPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 14:51
*/
// 产品 -> product
public class House {
private String baise;
private String wall;
private String roofed;

public String getBaise() {
return baise;
}

public void setBaise(String baise) {
this.baise = baise;
}

public String getWall() {
return wall;
}

public void setWall(String wall) {
this.wall = wall;
}

public String getRoofed() {
return roofed;
}

public void setRoofed(String roofed) {
this.roofed = roofed;
}

@Override
public String toString() {
return "House{" +
"baise='" + baise + '\'' +
", wall='" + wall + '\'' +
", roofed='" + roofed + '\'' +
'}';
}
}

HouseBuilder(抽象建造者)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package builderPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 14:56
*/
public abstract class HouseBuilder {

House house = new House();

// 将建造流程写好,抽象的方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();

// 建造房子,将产品(房子)返回
public House buildHouse() {
return house;
}
}

CommonsBuilder(具体建造者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
package builderPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 15:04
*/
public class CommonsHouse extends HouseBuilder{
@Override
public void buildBasic() {
house.setBaise("普通房子打地基10米");
System.out.println("普通房子打地基10米");
}

@Override
public void buildWalls() {
house.setWall("普通房子砌墙10cm");
System.out.println("普通房子砌墙10cm");
}

@Override
public void roofed() {
house.setRoofed("普通房子屋顶");
System.out.println("普通房子屋顶");
}
}

HighBuilding(具体建造者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
package builderPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 15:06
*/
public class HighBuilding extends HouseBuilder{
@Override
public void buildBasic() {
house.setBaise("高楼打地基100米");
System.out.println("高楼打地基100米");
}

@Override
public void buildWalls() {
house.setWall("高楼砌墙30cm");
System.out.println("高楼砌墙30cm");
}

@Override
public void roofed() {
house.setRoofed("高楼建造房顶");
System.out.println("高楼建造房顶");
}
}

HouseDirector(指挥者)

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
package builderPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 15:08
*/
// 指挥者,这里去指定制作流程,返回产品
public class HouseDirector {
HouseBuilder houseBuilder = null;

// 构造器传入houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}

// 通过setter 传入houseBuilder
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}

// 如何处理建造房子的流程,交给指挥者
public House ConstructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}

}

Client

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
package builderPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-23 15:13
*/
public class Client {
public static void main(String[] args) {
// 准备盖普通的房子
CommonsHouse commonsHouse = new CommonsHouse();
// 准备创建房子的指挥者
HouseDirector director = new HouseDirector(commonsHouse);
// 完成盖房子,返回产品(房子)
House house = director.ConstructHouse();
System.out.println(house);

System.out.println("-------------");
// 准备盖高楼
HighBuilding highBuilding = new HighBuilding();
// 准备创建房子的指挥者
HouseDirector director2 = new HouseDirector(highBuilding);
// 完成盖房子,返回产品(房子)
House house2 = director2.ConstructHouse();
System.out.println(house2);
}
}


建造者模式在JDK的应用和源码分析

1
2
3
4
5
6
7
8
9
public interface Appendable {

Appendable append(CharSequence csq) throws IOException;

Appendable append(CharSequence csq, int start, int end) throws IOException;

Appendable append(char c) throws IOException;
}

image-20230323154742427

  1. Appendable 接口定义了多个append方法(抽象方法),即Appendable为抽象建造者,定义了抽象方法
  2. AbstractStringBuilder 实现了Appendable 接口方法,这里的AbstractStringBuilder 已经是建造者, 只是不能实例化
  3. StringBuilder 即充当了指挥者角色,同时充当了具体的建造者,建造方法的实现是由AbstractStringBuilder 完成,而StringBuilder继承了AbstractStringBuilder

总结

  • 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  • 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
  • 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
  • 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.
  • 抽象工厂模式VS建造者模式
    抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品

结构型模式

适配器模式(⭐⭐⭐⭐)

image-20230324141438063

  1. 适配器模式(Adapter Patterm)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
  2. 适配器模式属于结构型模式
  3. 主要分为三类:类适配器模式对象适配器模式接口适配器模式

工作原理

  • 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
  • 从用户的角度看不到被适配者,是解耦的
  • 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
  • 用户收到反馈结果,感觉只是和目标接口交互,如图

image-20230324141738584

类适配器模式

基本介绍: Adapter类,通过继承src类,实现dst类接口,完成src->dst 的适配。

适配器模式应用实例

以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电相当于src (即被适配者),我们的目dst(即目标)是5V直流电

image-20230324141900304

代码实现

Voltage220V(被适配的类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package adapterPattern.classAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 14:19
*/
// 被适配的类
public class Voltage220V {

public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}

IVoltage5V(适配接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
package adapterPattern.classAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 14:22
*/
// 适配接口
public interface IVoltage5V {

public int output5V();
}

VoltageAdapter(适配器类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package adapterPattern.classAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 14:23
*/
// 适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
int src = output220V();// 获取220v电压
int dist = src / 44; // 转为5v
return dist;
}
}

Phone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package adapterPattern.classAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 14:25
*/
public class Phone {
// 充电
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.output5V() == 5) {
System.out.println("电压为5v,可以正常充电");
} else {
System.out.println("电压异常不能充电");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package adapterPattern.classAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 14:27
*/
public class Client {
public static void main(String[] args) {
System.out.println("===适配器模式===");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}

  1. Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点,因为这要求dst必须是接口,有一定局限性;
  2. src类的方法在 Adapter中都会暴露出来,也增加了使用的成本。
  3. 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。

对象适配器模式

  1. 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。即:持有src类,实现 dst类接口,完成src->dst的适配
  2. 根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。
  3. 对象适配器模式是适配器模式常用的一种

对象适配器模式应用实例

​ 以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter, 220V交流电相当于src (即被适配者),我们的目dst(即目标)是5V直流电,使用对象适配器模式完成。

image-20230324145737503

代码实现

Voltage220V(被适配的类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package adapterPattern.ObjectAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 14:19
*/
// 被适配的类
public class Voltage220V {

public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}

IVoltage5V(适配接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
package adapterPattern.ObjectAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 14:22
*/
// 适配接口
public interface IVoltage5V {

public int output5V();
}

VoltageAdapter(对象适配器类)

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
package adapterPattern.ObjectAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 14:23
*/
// 对象适配器类
public class VoltageAdapter implements IVoltage5V {

private Voltage220V voltage220V; // 关联关系--聚合

public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}

@Override
public int output5V() {
int dist = 0;
if (voltage220V != null ) {
int src = voltage220V.output220V();// 获取220v电压
System.out.println("使用对象适配器");
dist = src / 44; // 转为5v
System.out.println("适配完成,输出的电压为: " + dist);
}

return dist;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package adapterPattern.ObjectAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 14:25
*/
public class Phone {
// 充电
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.output5V() == 5) {
System.out.println("电压为5v,可以正常充电");
} else {
System.out.println("电压异常不能充电");
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package adapterPattern.ObjectAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 14:27
*/
public class Client {
public static void main(String[] args) {
System.out.println("===适配器模式===");
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
  • 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。
    根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
  • 使用成本更低, 更灵活。

接口适配器模式

  1. 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式
  2. 核心思路: 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个**默认实现(空方法)**,那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
  3. 适用于一个接口不想使用其所有的方法的情况。

接口适配器模式应用实例

  1. Android中的属性动画ValueAnimator类可以通过addListener(AnimatorListener listener)方法添加监听器,那么常规写法如右:
  2. 有时候我们不想实现Animator.AnimatorListener接口的全部方法,我们只想监听onAnimationStart,我们会如下写

image-20230324153250330

  1. AnimatorListenerAdapter 类,就是-一个接口适配器,代码如右图:它空实现了Animator.AnimatorListener类(src)的所有方法.
  2. AnimatorListener 是一个接口.

image-20230324154115673

  1. 程序里的匿名内部类就是Listener具体实现类.

image-20230324154205288

image-20230324154222580

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package adapterPattern.InterfaceAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 15:56
*/
public interface Interface1 {
public void m1();
public void m2();
public void m3();
public void m4();
}

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
package adapterPattern.InterfaceAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 16:02
*/
//在AbsAdapter我们将Interface4 的方法进行默认实现
public class AbsAdapter implements Interface1{
@Override
public void m1() {

}

@Override
public void m2() {

}

@Override
public void m3() {

}

@Override
public void m4() {

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package adapterPattern.InterfaceAdapter;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 16:04
*/
public class Client {
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter(){
@Override
public void m1() {
System.out.println("使用了m1的方法");
}
};
absAdapter.m1();
}

}

适配器模式在SpringMVC框架应用的源码剖析

  1. SpringMvc 中的HandlerAdapter,就使用了适配器模式
  2. SpringMVC 处理请求的流程回顾
  3. 使用HandlerAdapter的原因分析:
    可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用ifelse来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,
    就得修改原来的代码,这样违背了OCP原则。
  4. 代码分析+Debug源码

image-20230324163517853

动手写SpringMVC通过适配器设计模式获取到对应的Controller的源码

image-20230324163604030

image-20230324171828498

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package Spring.springmvc;

//多种Controller实现
public interface Controller {

}

class HttpController implements Controller {
public void doHttpHandler() {
System.out.println("http...");
}
}

class SimpleController implements Controller {
public void doSimplerHandler() {
System.out.println("simple...");
}
}

class AnnotationController implements Controller {
public void doAnnotationHandler() {
System.out.println("annotation...");
}
}

HandlerAdapter

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
package Spring.springmvc;

///定义一个Adapter接口
public interface HandlerAdapter {
public boolean supports(Object handler);

public void handle(Object handler);
}

// 多种适配器类

class SimpleHandlerAdapter implements HandlerAdapter {

public void handle(Object handler) {
((SimpleController) handler).doSimplerHandler();
}

public boolean supports(Object handler) {
return (handler instanceof SimpleController);
}

}

class HttpHandlerAdapter implements HandlerAdapter {

public void handle(Object handler) {
((HttpController) handler).doHttpHandler();
}

public boolean supports(Object handler) {
return (handler instanceof HttpController);
}

}

class AnnotationHandlerAdapter implements HandlerAdapter {

public void handle(Object handler) {
((AnnotationController) handler).doAnnotationHandler();
}

public boolean supports(Object handler) {

return (handler instanceof AnnotationController);
}

}
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
package Spring.springmvc;

import java.util.ArrayList;
import java.util.List;

public class DispatchServlet {

public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();

public DispatchServlet() {
handlerAdapters.add(new AnnotationHandlerAdapter());
handlerAdapters.add(new HttpHandlerAdapter());
handlerAdapters.add(new SimpleHandlerAdapter());
}

public void doDispatch() {

// 此处模拟SpringMVC从request取handler的对象,
// 适配器可以获取到希望的Controller
HttpController controller = new HttpController();
// AnnotationController controller = new AnnotationController();
//SimpleController controller = new SimpleController();
// 得到对应适配器
HandlerAdapter adapter = getHandler(controller);
// 通过适配器执行对应的controller对应方法
adapter.handle(controller);

}

public HandlerAdapter getHandler(Controller controller) {
//遍历:根据得到的controller(handler), 返回对应适配器
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(controller)) {
return adapter;
}
}
return null;
}

public static void main(String[] args) {
new DispatchServlet().doDispatch(); // http...
}

}

总结

  1. 三种命名方式,是根据src是以怎样的形式给到Adapter (在Adapter里的形式)来命名的。
  2. 类适配器: 以类给到,在Adapter里,就是将src当做类,继承
    对象适配器: 以对象给到,在Adapter里,将src作为一个对象,持有
    接口适配器: 以接口给到,在Adapter里,将src作为一个接口,实现
  3. Adapter 模式最大的作用还是将原本不兼容的接口融合在一起工作。
  4. 实际开发中, 实现起来不拘泥于我们讲解的三种经典形式

桥接模式(⭐⭐⭐)

现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:

image-20230324194133934

传统方案解决手机操作问题

image-20230324194217073

  1. 扩展性问题**(类爆炸)**,如果我们再增加手机的样式(旋转式), 就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
  2. 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
  3. 解决方案-使用桥接模式

桥接模式(Bridge)-基本介绍

  1. 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
  2. 是一种结构型设计模式
  3. Bridge 模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的
    功能扩展

image-20230324194521759

  1. Client 类:桥接模式的调用者
  2. 抽象类( Abstraction) :维护了Implementor /即它的实现类ConcreteImplementor..二者是聚合关系,Abstraction充当桥接类
  3. RefinedAbstraction: 是Abstraction 抽象类的子类
  4. Implementor :行为实现类的接口
  5. ConcretelmplementorA/B :行为的具体实现类
  6. 从UML图:这里的抽象类和接口是聚合的关系,其实调用和被调用关系

代码实现

image-20230324194727212

1
2
3
4
5
6
7
8
9
10
11
12
13
package BridgePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 19:50
*/
// 接口
public interface Brand {
public void open();
public void close();
public void call();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package BridgePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 19:57
*/
public class Xiaomi implements Brand{
@Override
public void open() {
System.out.println("小米手机开机");
}

@Override
public void close() {
System.out.println("小米手机关机");
}

@Override
public void call() {
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
package BridgePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 19:58
*/
public class ViVo implements Brand{
@Override
public void open() {
System.out.println("ViVo手机开机");
}

@Override
public void close() {
System.out.println("ViVo手机关机");
}

@Override
public void call() {
System.out.println("ViVo手机打电话");
}
}
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
package BridgePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 19:51
*/
public abstract class Phone {
// 组合品牌
Brand brand;

public Phone(Brand brand) {
this.brand = brand;
}

protected void open() {
this.brand.open();
}
protected void close() {
this.brand.close();
}
protected void call() {
this.brand.call();
}

}
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
package BridgePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 19:53
*/
public class RotatePhone extends Phone{
public RotatePhone(Brand brand) {
super(brand);
}

private String typeName = "旋转";

@Override
protected void open() {
super.open();
System.out.println(typeName +"手机打开");
}

@Override
protected void close() {
super.close();
System.out.println(typeName +"手机关闭");
}

@Override
protected void call() {
super.call();
System.out.println(typeName +"手机打电话");
}
}
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
package BridgePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 19:53
*/
public class UprightPhone extends Phone{
public UprightPhone(Brand brand) {
super(brand);
}

private String typeName = "直立";

@Override
protected void open() {
super.open();
System.out.println(typeName +"手机打开");
}

@Override
protected void close() {
super.close();
System.out.println(typeName +"手机关闭");
}

@Override
protected void call() {
super.call();
System.out.println(typeName +"手机打电话");
}
}
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
package BridgePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-24 19:59
*/
public class Client {
public static void main(String[] args) {
Phone phone = new UprightPhone(new ViVo());
phone.open();
phone.close();
phone.call();
System.out.println("================");
Phone phone1 = new UprightPhone(new Xiaomi());
phone1.open();
phone1.close();
phone1.call();
System.out.println("================");
Phone phone2 = new RotatePhone(new ViVo());
phone2.open();
phone2.close();
phone2.call();
}
}

桥接模式在JDBC的源码剖析

  1. Jdbc 的Driver 接口,如果从桥接模式来看,Driver 就是-一个接口,下面可以有MySQL的Driver,Oracle 的Driver,这些就可以当做实现接口类
    2)代码分 析+Debug源码

image-20230324201708955

image-20230324201841888

总结

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
  4. 桥模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
  5. 桥接模式要求正确识别出系统中两个独立变化的维度(抽象、和实现),因此其使用范围有-定的局限性,即需要有这样的应用场景。

应用场景

1)- JDBC驱动程序
2)- 银行转账系统

  • 转账分类:网上转账,柜台转账,AMT转账
  • 转账用户类型:普通用户,银卡用户,金卡用户..
    • 消息管理
  • 消息类型:即时消息,延时消息
  • 消息分类:手机短信,邮件消息,QQ消息…

装饰者模式(⭐⭐⭐)

动态的给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。

问题描述:

  1. 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
  2. 调料:Milk、Soy(豆浆)、Chocolate
  3. 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
  4. 使用O0的来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合。

方案1-解决星巴克咖啡订单项目

image-20230325154008144

  1. Drink是一个抽象类,表示饮料
  2. des就是对咖啡的描述,比如咖啡的名字
  3. cost()方法就是计算费用,Drink类中做成一个抽象方法.
  4. Decaf 就是单品咖啡,继承 Drink,并实现cost
  5. Espress && Milk 就是单品咖啡+调料,这个组合很多
  6. 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸

方案2-解决星巴克咖啡订单(好点)

  1. 前面分析到方案1因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多。从而提高项目的维护性(如图)

image-20230325154842156

  1. 说明: milk,soy,chocolate可以设计为Boolean,表示是否要添加相应的调料.

分析:

  1. 方案2可以控制类的数量,不至于造成很多的类
  2. 在增加或者删除调料种类时,代码的维护量很大
  3. 考虑到用户可以添加多份调料时,可以将 hasMilk返回一个对应int
  4. 考虑使用装饰者模式

装饰者模式定义

  1. 装饰者模式: 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
  2. 这里提到的动态的将新功能附加到对象和ocp原则,在后面的应用实例上会以代码的形式体现,请同学们注意体会。

装饰者模式原理

  1. 装饰者模式就像打包一个快递.
    主体:比如:陶瓷、衣服(Component)//被装饰者
    包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)
  2. Component主体:比如类似前面的Drink
  3. ConcreteComponent 和Decorator
    ConcreteComponent:具体的主体,比如前面的各个单品咖啡
  4. Decorator:装饰者,比如各调料.
    在如图的Component与ConcreteComponent之间,如果ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类。

image-20230325160327373

代码实现

image-20230326141445902

image-20230326141523153

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
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-25 16:07
*/
// Component主体
public abstract class Drink {

public String des; // 藐视
private float price = 0.0f;

public String getDes() {
return des;
}

public void setDes(String des) {
this.des = des;
}

public float getPrice() {
return price;
}

public void setPrice(float price) {
this.price = price;
}

// 计算费用的抽象方法
// 子类来实现
public abstract float cost();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-25 16:13
*/
public class Coffee extends Drink{
@Override
public float cost() {
return super.getPrice();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 14:21
*/
public class ShortBlack extends Coffee{
public ShortBlack() {
setDes("shortblack");
setPrice(5.0f);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 14:19
*/
public class LongBlack extends Coffee{
public LongBlack() {
setDes("longblack");
setPrice(5.0f);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 14:16
*/
public class DeCaf extends Coffee{

public DeCaf() {
setDes("无因咖啡");
setPrice(1.0f);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 14:18
*/
public class Espresso extends Coffee{
public Espresso() {
setDes("意大利咖啡");
setPrice(6.0f);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-25 16:09
*/
public class Decorator extends Drink{
private Drink obj;

public Decorator(Drink obj) { // 聚合
this.obj = obj;
}

@Override
public float cost() {
return super.getPrice() + obj.cost();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 14:22
*/
public class Milk extends Decorator{

public Milk(Drink obj) {
super(obj);
setDes("牛奶");
setPrice(2.0f);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 14:23
*/
public class Soy extends Decorator{
public Soy(Drink obj) {
super(obj);
setDes("豆浆");
setPrice(1.5f);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 14:25
*/
public class Chocolate extends Decorator{
public Chocolate(Drink obj) {
super(obj);
setDes("巧克力");
setPrice(3.0f);
}
}

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
package DecoratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 14:28
*/
public class CoffeeBar {
public static void main(String[] args) {
// 装饰者模式下单: 2份巧克力 + 一份牛奶的LongBlack

// 点一份LongBlack
Drink order = new LongBlack();
System.out.println("费用:" + order.cost());
System.out.println("描述:" + order.getDes());

// 加一份牛奶
order = new Milk(order);
System.out.println("加一份牛奶,费用:" + order.cost());
System.out.println("加一份牛奶,描述:" + order.getDes());

// 加一份巧克力
order = new Chocolate(order);
System.out.println("一份巧克力 加一份牛奶,费用:" + order.cost());
System.out.println("一份巧克力 加一份牛奶,描述:" + order.getDes());
// 加一份巧克力
order = new Chocolate(order);
System.out.println("2份巧克力 加一份牛奶,费用:" + order.cost());
System.out.println("2份巧克力 加一份牛奶,描述:" + order.getDes());

System.out.println("====================");
Drink order2 = new DeCaf();
System.out.println("无因咖啡,费用:" + order2.cost());
System.out.println("无因咖啡,描述:" + order2.getDes());

order2 = new Milk(order2);
System.out.println("无因咖啡 加一份牛奶,费用:" + order2.cost());
System.out.println("无因咖啡 加一份牛奶,描述:" + order2.getDes());
}
}

image-20230326144455937

装饰者模式在JDK应用的源码分析

image-20230326152110330

image-20230326152944858

image-20230326153018100

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
package JDK_SRC;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 15:23
*/
public class Decorator {
public static void main(String[] args) throws IOException {
//说明
// 1. InputStream是抽象类,类似我们前面讲的Drink
// 2. FileInputStream是 InputStream子类,类似我们前面的DeCaf, LongBlack1l
// 3. FilterInputStream是InputStream子类:类似我们前面的 Decorator修饰者
// 4. DataInputStream是 FilterInputStream子类,具体的修饰者,类似前面的Milk, Soy等l1
// 5.FilterInputStream类有protected volatile InputStream in;即含被装饰者
// 6.分析得出在jdk 的 io体系中,就是使用装饰者模式

DataInputStream dis = new DataInputStream(new FileInputStream("d:\\abc.txt"));
System.out.println(dis.read());
dis.close();
}
}

组合模式(⭐⭐⭐⭐)

image-20230326172439772

传统方式

image-20230326172521600

传统方式存在的问题:

1)将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
2)实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系,因此这种方案,不能很好实现的管理的操作,比如对学院、系的添加,删除,遍历等
3)解决方案: 把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。=> 组合模式

组合模式基本介绍

  1. 组合模式(Composite Patterm) ,又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
  2. 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
  3. 这种类型的设计模式属于结构型模式。
  4. 组合 模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象

组合模式原理类图

image-20230326190507655

  1. Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理,Component子部件, Component可以是抽象类或者接口
  2. Leaf: 在组合中表示叶子节点,叶子节点没有子节点
  3. Composite :非叶子节点,用于存储子 部件,在 Component接口中实现 子部件的相关操作,比如增加(add),删除。

代码实现

image-20230326190918338

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
package CompositePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 16:40
*/
public abstract class OrganizationComponent {
private String name; // 名字
private String des; // 说明

public void add(OrganizationComponent organizationComponent) {
// 默认实现
throw new UnsupportedOperationException();
}

public void remove(OrganizationComponent organizationComponent) {
// 默认实现
throw new UnsupportedOperationException();
}
// 构造器

public OrganizationComponent(String name, String des) {
this.name = name;
this.des = des;
}

public String getName() {
return name;
}

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

public String getDes() {
return des;
}

public void setDes(String des) {
this.des = des;
}
// 方法print做成抽象的,子类需要实现
public abstract void 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package CompositePattern;

import java.util.ArrayList;
import java.util.List;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 16:53
*/
public class University extends OrganizationComponent{

List<OrganizationComponent> organizationComponents = new ArrayList<>();

public University(String name, String des) {
super(name, des);
}

// 重写add
@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}

// 重写move
@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}

@Override
public String getDes() {
return super.getDes();
}

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

@Override
public void print() {
System.out.println("------------" + getName() + "------------");
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package CompositePattern;

import java.util.ArrayList;
import java.util.List;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 17:06
*/
public class Colleage extends OrganizationComponent{

List<OrganizationComponent> organizationComponents = new ArrayList<>();

public Colleage(String name, String des) {
super(name, des);
}

// 重写add
@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}

// 重写move
@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}

@Override
public String getDes() {
return super.getDes();
}

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

@Override
public void print() {
System.out.println("------------" + getName() + "------------");
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.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
package CompositePattern;

import java.util.ArrayList;
import java.util.List;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 17:07
*/
public class Department extends OrganizationComponent{

public Department(String name, String des) {
super(name, des);
}

@Override
public String getDes() {
return super.getDes();
}

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

@Override
public void print() {
System.out.println(getName());
}
}


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
package CompositePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 17:10
*/
public class Client {
public static void main(String[] args) {
OrganizationComponent university = new University("清华大学","985/211");
OrganizationComponent colleage1 = new Colleage("计算机学院","计算机学院");
OrganizationComponent colleage2 = new Colleage("软件学院","软件学院");
university.add(colleage1);
university.add(colleage2);
colleage1.add(new Department("计算机技术", "111"));
colleage1.add(new Department("人工智能", "111"));
colleage1.add(new Department("大数据", "111"));
colleage2.add(new Department("软件工程", "111"));
university.print();
System.out.println("================");
colleage1.print();

}
}

组合模式在JDK集合的源码分析

Java的集合类-HashMap就使用了组合模式

image-20230326193808499

image-20230326193827834

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
package JDK_SRC;

import java.util.HashMap;

/***
* @author dep
* @version 1.0
* @date 2023-03-26 19:39
*/
public class Composite {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<>();
map.put(1,"红楼梦");


HashMap<Integer,String> map1 = new HashMap<>();
map1.put(2,"三国演义");
map1.put(3,"水浒传");
map1.put(4,"西游记");

map.putAll(map1);
System.out.println(map);
}
}

总结

  1. 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
  2. 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
  3. 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
  4. 要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式.
  5. 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

外观模式(⭐⭐⭐⭐⭐)

组建一个家庭影院:
DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:;直接用遥控器:统筹各设备开关
开爆米花机
放下屏幕开投影仪开音响
开DVD,选dvd去拿爆米花调暗灯光播放
观影结束后,关闭各种设备

传统方式解决影院管理

image-20230327213359768

  1. 在ClientTest 的main方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程
    混乱,没有清晰的过程
  2. 不利于在ClientTest中,去维护对子系统的操作
  3. 解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法
    ready, play, pause, end ),用来访问子系统中的一群接口
  4. 也就是说就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发
    生调用,而无需关心这个子系统的内部细节→外观模式

外观模式基本介绍

  1. 外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了
    一个高层接口,这个接口使得这一子系统更加容易使用
  2. 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节

image-20230327214131950

  • 外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代
    理给适当子系统对象
  • 调用者(Client):外观接口的调用者
  • 子系统的集合:指模块或者子系统,处理Facade对象指派的任务,他是功能的实际提供者

image-20230327214539518

代码实现

image-20230327214620848

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
package FacadePattern;

public class DVDPlayer {

// 单例模式 饿汉式
private static DVDPlayer instance = new DVDPlayer();

public static DVDPlayer getInstanc() {
return instance;
}

public void on() {
System.out.println(" dvd on ");
}
public void off() {
System.out.println(" dvd off ");
}

public void play() {
System.out.println(" dvd is playing ");
}

//....
public void pause() {
System.out.println(" dvd pause ..");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package FacadePattern;

public class Popcorn {

private static Popcorn instance = new Popcorn();

public static Popcorn getInstance() {
return instance;
}

public void on() {
System.out.println(" popcorn on ");
}

public void off() {
System.out.println(" popcorn ff ");
}

public void pop() {
System.out.println(" popcorn is poping ");
}
}

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
package FacadePattern;

public class Projector {

private static Projector instance = new Projector();

public static Projector getInstance() {
return instance;
}

public void on() {
System.out.println(" Projector on ");
}

public void off() {
System.out.println(" Projector ff ");
}

public void focus() {
System.out.println(" Projector is Projector ");
}

//...
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package FacadePattern;

public class Screen {

private static Screen instance = new Screen();

public static Screen getInstance() {
return instance;
}

public void up() {
System.out.println(" Screen up ");
}

public void down() {
System.out.println(" Screen down ");
}


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package FacadePattern;

public class Stereo {

private static Stereo instance = new Stereo();

public static Stereo getInstance() {
return instance;
}

public void on() {
System.out.println(" Stereo on ");
}

public void off() {
System.out.println(" Screen off ");
}

public void up() {
System.out.println(" Screen up.. ");
}

//...
}
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
package FacadePattern;

public class TheaterLight {

private static TheaterLight instance = new TheaterLight();

public static TheaterLight getInstance() {
return instance;
}

public void on() {
System.out.println(" TheaterLight on ");
}

public void off() {
System.out.println(" TheaterLight off ");
}

public void dim() {
System.out.println(" TheaterLight dim.. ");
}

public void bright() {
System.out.println(" TheaterLight bright.. ");
}
}
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
package FacadePattern;

public class HomeTheaterFacade {

private TheaterLight theaterLight;
private Popcorn popcorn;
private Stereo stereo;
private Projector projector;
private Screen screen;
private DVDPlayer dVDPlayer;


public HomeTheaterFacade() {
super();
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.stereo = Stereo.getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
this.dVDPlayer = DVDPlayer.getInstanc();
}


public void ready() {
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
stereo.on();
dVDPlayer.on();
theaterLight.dim();
}

public void play() {
dVDPlayer.play();
}

public void pause() {
dVDPlayer.pause();
}

public void end() {
popcorn.off();
theaterLight.bright();
screen.up();
projector.off();
stereo.off();
dVDPlayer.off();
}

}
1
2
3
4
5
6
7
8
9
10
11
package FacadePattern;

public class Client {

public static void main(String[] args) {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
homeTheaterFacade.ready();
homeTheaterFacade.play();
homeTheaterFacade.end();
}
}

外观模式在MyBatis框架应用的源码分析

image-20230327223459904

总结

  1. 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
  2. 外观模式对客户端与子系统的耦合关系–解耦,让子系统内部的模块更易维护和扩展
  3. 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
  4. 当系统需要进行分层设计时,可以考虑使用Facade模式
  5. 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性
  6. 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。

享元模式(⭐)

需求:

小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:

  1. 有客户要求以新闻的形式发布
  2. 有客户人要求以博客的形式发布
  3. 有客户希望以微信公众号的形式发布

传统解决方案:

image-20230329155739471

问题:

  1. 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
  2. 解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源
    都可以达成共享,减少服务器资源
  3. 对于代码来说,由于是一份实例,维护和扩展都更加容易
  4. 上面的解决思路就可以使用享元模式来解决

享元模式基本介绍

  1. 享元模式(Flyweight Pattern)也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象
  2. 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
  3. 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
  4. 享元模式经典的应用场景就是池技术了,String常量池数据库连接池缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式

享元模式的原理类图

image-20230329160921493

  1. FlyWeight 是抽象的享元角色,他是产品的抽象类,同时定义出对象的外部状态内部状态(后面介绍)的接口或实现
  2. ConcreteFlyWeight是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
  3. UnSharedConcreteFlyWeight是不可共享的角色,一般不会出现在享元工厂。
  4. FlyWeightFactory享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象方法

内部状态和外部状态

比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态

享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两
个部分:内部状态和外部状态

  1. 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
  2. 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。

举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有
限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题

代码实现

image-20230329161414977

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package FlyweightPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 16:19
*/
public class User {
private String name;

public User(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
6
7
8
9
10
package FlyweightPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 16:18
*/
public abstract class WebSite {
public abstract void use(User user);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package FlyweightPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 16:20
*/
public class ConcreteWebSite extends WebSite {
// 共享的部分,内部状态
private String type = "";

public ConcreteWebSite(String type) {
this.type = type;
}

@Override
public void use(User user) {
System.out.println("网站的发布形式是:" + type + "使用者是:" + user.getName());
}
}
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
package FlyweightPattern;

import java.util.HashMap;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 16:23
*/
// 网站工厂类
public class WebSiteFactory {

// 集合,充当池的作用
private HashMap<String,ConcreteWebSite> pools = new HashMap<>();

// 根据网站的类型,返回一个网站,如果没有就创建一个,并放入到池中,并返回。
public WebSite getWebSite(String type) {
if ( !pools.containsKey(type)) {
pools.put(type, new ConcreteWebSite(type));
}
return pools.get(type);
}

// 获取网站的总数
public int getWebSiteCount() {
return pools.size();
}
}

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
package FlyweightPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 16:30
*/
public class Client {
public static void main(String[] args) {
WebSiteFactory webSiteFactory = new WebSiteFactory();

WebSite webSite = webSiteFactory.getWebSite("微博");
webSite.use(new User("张三"));

WebSite webSite1 = webSiteFactory.getWebSite("公众号");
webSite1.use(new User("李四"));

WebSite webSite2 = webSiteFactory.getWebSite("微博");
webSite2.use(new User("张三1"));

WebSite webSite3 = webSiteFactory.getWebSite("微博");
webSite3.use(new User("张三2"));
WebSite webSite4 = webSiteFactory.getWebSite("微博");
webSite4.use(new User("张三3"));

System.out.println(webSiteFactory.getWebSiteCount());

}
}

享元模式在JDK-Interger的应用源码分析

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 static void main(String[] args) {

// 如果Integer.valueOf(x)x在-128---127直接,就是使用享元模式返回,如果不在范围类,则仍然new

//小结:
//1.在valueOf方法中,先判断值是否在 IntegerCache 中,如果不在,就创建新的Integer(new),否则,就直接从缓存池返回
//2. valueOf方法,就使用到享元模式
//3.如果使用valueOf 方法得到一个Integer实例,范围在-128- 127,执行速度比 new 快

Integer x = Integer.valueOf(127);//得到x实例,类型
Integer y = new Integer(127);//得到y 实例,类型
Integer z = Integer.valueOf(127); //
Integer w = new Integer(127);
System.out.println(x.equals(y)); // 判断的是值 true
System.out.println(x == y ); // false
System.out.println(x == z ); // true
System.out.println(w == x ); // false
System.out.println(w == y ); // false


Integer x1 = Integer.valueOf(200);
Integer x2 = Integer.valueOf(200);
System.out.println("x1==x2 = " + (x1 == x2));

}

image-20230329165429172

总结

  1. 在享元模式这样理解,“享”就表示共享“元”表示对象
  2. 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
  3. 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
  4. 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
  5. 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
  6. 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。7)享元模式经典的应用场景是需要缓冲池的场景,比如String常量池、数据库连接池

代理模式(⭐⭐⭐⭐)

代理模式(Proxy)

  1. 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处
    是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
  2. 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
  3. 代理模式有不同的形式,主要有三种静态代理动态代理(JDK 代理、接口代理)Cglib代理(可以在内存
    动态的创建对象,而不需要实现接口,他是属于动态代理的范畴)。
  4. 代理模式示意图

image-20230329171657520

静态代理

​ 静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类

  1. 定义一个接口:ITeacherDao
  2. 目标对象TeacherDAO实现接口ITeacherDAO
  3. 使用静态代理方式,就需要在代理对象TeacherDAOProxy 中也实现ITeacherDAO
  4. 调用的时候通过调用代理对象的方法来调用目标对象.
  5. 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法

image-20230329171853713

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
package ProxyPattern.staticpattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 17:21
*/
// 接口
public interface ITeacherDao {
void teach(); // 授课
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package ProxyPattern.staticpattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 18:32
*/
public class TeacherDao implements ITeacherDao{
@Override
public void teach() {
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
package ProxyPattern.staticpattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 18:33
*/
public class TeacherDaoProxy implements ITeacherDao{

private ITeacherDao iTeacherDao; // 目标对象,通过接口来聚合

public TeacherDaoProxy(ITeacherDao iTeacherDao) {
this.iTeacherDao = iTeacherDao;
}

@Override
public void teach() {
System.out.println("开始代理,完成某些操作");
iTeacherDao.teach();
System.out.println("提交....");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package ProxyPattern.staticpattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 18:35
*/
public class Client {
public static void main(String[] args) {
// 创建目标代理对象(被代理对象)
TeacherDao teacherDao = new TeacherDao();

// 创建代理对象,同时将被代理对象传给代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);

// 执行的是代理对象的方法,代理对象再去调用目标对象方法
teacherDaoProxy.teach();
}
}

静态代理优缺点

  • 优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展

  • 缺点: 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类

    ​ 一旦接口增加方法,目标对象与代理对象都要维护

动态代理

动态代理模式的基本介绍

  1. 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
  2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
  3. 动态代理也叫做:JDK代理接口代理

JDK中生成代理对象的API

  1. 代理类所在包:java.lang.reflect.Proxy
  2. JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
    static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h )

代码实现

image-20230329192051268

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package ProxyPattern.dynamicProxy;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 19:22
*/
public interface ITeacherDao {

void teach();

void sayHello(String name);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package ProxyPattern.dynamicProxy;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 19:23
*/
public class TeacherDao implements ITeacherDao{
@Override
public void teach() {
System.out.println("开始授课....");
}

@Override
public void sayHello(String name) {
System.out.println("hi~" + name);
}
}

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
package ProxyPattern.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 19:27
*/
public class ProxyFactory {

// 维护一个,目标对象,Object
private Object target;

// 构造器,对target进行初始化
public ProxyFactory(Object target) {
this.target = target;
}

// 给目标对象生成一个代理对象
public Object getProxyInstance() {
/**
*
* public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
* 1. ClassLoader loader: 指定当前目前对象使用的类加载器,获取加载器的方法固定
* 2. Class<?> interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
* 3. InvocationHandler h: 事件处理,执行目标对象的方法时,会触发事情处理器办法,会把当前执行的目标对象方法作为参数传入
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK代理开始~~");
// 反射机制调用目标对象的方法
Object invoke = method.invoke(target, args);
System.out.println("JDK代理提交");
return invoke;
}
});
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package ProxyPattern.dynamicProxy;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 19:56
*/
public class Client {
public static void main(String[] args) {
// 创建目标对象
ITeacherDao target = new TeacherDao();

// 给目标,创建代理对象,可以转成ITeacherDao
ITeacherDao proxyInstance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();

proxyInstance.sayHello("张三");
System.out.println("==============");
proxyInstance.teach();
}
}

Cglib代理

Cglib代理模式的基本介绍

  1. 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实
    现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理

  2. Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib
    理归属到动态代理。

  3. Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP
    框架使用,例如Spring AOP,实现方法拦截

  4. 在AOP编程中如何选择代理模式:
    1.目标对象需要实现接口,用JDK代理

    2.目标对象不需要实现接口,用Cglib 代理

  5. Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类

Cglib代理模式实现步骤

  • 导入jar包

  • 在内存中动态构建子类,注意代理的类不能为final,否则报错
    java.lang.IllegalArgumentException:

  • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

代码实现

image-20230329204247516

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package ProxyPattern.CglibProxy;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 20:46
*/
public class TeacherDao {
public void teach() {
System.out.println("老师授课中...,我是cglib代理,我不需要实现接口");
}

public void sayHello(String name) {
System.out.println("你好~~~" + name);
}
}

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
package ProxyPattern.CglibProxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 20:48
*/
public class ProxyFactory implements MethodInterceptor {

// 维护一个目标对象
private Object target;

// 构造器,传入一个被代理的对象
public ProxyFactory(Object target) {
this.target = target;
}

// 返回一个对象:是target对象的代理对象
public Object getProxyInstance() {
// 1.创建一个工具类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(target.getClass());
// 3. 设置回调函数
enhancer.setCallback(this);
// 4.创建子类对象,代理对象
return enhancer.create();
}


// 重写intercept方法,会调用目标对象的方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib代理模式~~开始");
Object invoke = method.invoke(target, args);
System.out.println("Cglib代理模式~~结束");
return invoke;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package ProxyPattern.CglibProxy;

/***
* @author dep
* @version 1.0
* @date 2023-03-29 20:55
*/
public class Client {
public static void main(String[] args) {
TeacherDao teacherDao = new TeacherDao();

TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(teacherDao).getProxyInstance();
proxyInstance.teach();
proxyInstance.sayHello("张三");

}
}

几种常见的代理模式介绍一几种变体

  1. 防火墙代理
    内网通过代理穿透防火墙,实现对公网的访问。
  2. 缓存代理
    比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。
  3. 远程代理
    远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
  4. 同步代理:主要使用在多线程编程中,完成多线程间同步工作

行为型模式

模板方法模式(⭐⭐⭐)

问题:

  1. 制作豆浆的流程选材—>添加配料—>浸泡—>放到豆浆机打碎
  2. 通过添加不同的配料,可以制作出不同口味的豆浆
  3. 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
  4. 请使用模板方法模式完成(说明:因为模板方法模式,比较简单,很容易就想到这个方案,因此就直接使用,
    不再使用传统的方案来引出模板方法模式)

模板方法模式基本介绍

  1. 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
  2. 简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变
    个算法的结构,就可以重定义该算法的某些特定步骤
  3. 这种类型的设计模式属于行为型模式。

模板方法模式原理类图

image-20230330150426251

  1. AbstractClass 抽象类,类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现其它的抽象方法operationr2,3,4
  2. ConcreteClass实现抽象方法 operationr2,3,4,以完成算法中特点子类的步骤

代码实现

image-20230330150558438

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
package TemplatePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 15:07
*/
// 抽象类, 表示豆浆
public abstract class SoyaMilk {

// 模板方法,make,模板方法可以做成final类,不让子类覆盖
final void make() {
select();
addCondiments();
soak();
beat();
}

// 选材料
void select() {
System.out.println("第一步:选择新鲜黄豆");
}
// 添加不同的配料,抽象方法,子类去实现
abstract void addCondiments();

// 浸泡
void soak() {
System.out.println("第三步:黄豆和配料开始浸泡,需要3小时");
}

void beat() {
System.out.println("第四步:黄豆和配料放到豆浆机区打碎");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package TemplatePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 15:14
*/
public class RedBeanSoyaMilk extends SoyaMilk{
@Override
void addCondiments() {
System.out.println("加入上好的红豆");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package TemplatePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 15:14
*/
public class PeanutSoyaMilk extends SoyaMilk{
@Override
void addCondiments() {
System.out.println(" 加入上好的花生 ");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package TemplatePattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 15:15
*/
public class Client {
public static void main(String[] args) {
System.out.println("-----制作红豆豆浆-------");
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();

System.out.println("-----制作花生豆浆-------");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
}
}

模板方法模式的钩子方法

  • 在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。
  • 还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面
    的模板方法进行改造
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
package TemplatePattern.improve;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 15:07
*/
// 抽象类, 表示豆浆
public abstract class SoyaMilk {

// 模板方法,make,模板方法可以做成final类,不让子类覆盖
final void make() {
select();
if (customerWantCondiments()){
addCondiments();
}
soak();
beat();
}

// 选材料
void select() {
System.out.println("第一步:选择新鲜黄豆");
}
// 添加不同的配料,抽象方法,子类去实现
abstract void addCondiments();

// 浸泡
void soak() {
System.out.println("第三步:黄豆和配料开始浸泡,需要3小时");
}

void beat() {
System.out.println("第四步:黄豆和配料放到豆浆机区打碎");
}

// 钩子方法,决定是否要添加配料
Boolean customerWantCondiments() {
return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package TemplatePattern.improve;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 15:40
*/
public class PureSoyaMilk extends SoyaMilk{
@Override
void addCondiments() {
// 空实现
}

@Override
Boolean customerWantCondiments() {
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
package TemplatePattern.improve;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 15:15
*/
public class Client {
public static void main(String[] args) {
System.out.println("-----制作红豆豆浆-------");
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();

System.out.println("-----制作花生豆浆-------");
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();

System.out.println("-----制作纯豆浆-------");
SoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
}
}

模板方法模式在Spring框架应用的源码分析

image-20230330155832542

image-20230330155712321

总结

  1. 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方
    法或者已经实现的某些步骤,子类就会继承这些修改
  2. 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
  3. 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
  4. 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
  5. 一般模板方法都加上 final关键字,防止子类重写模板方法.
  6. 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其
    个别步骤在实现时可能不同,通常考虑用模板方法模式来处理

命令模式(⭐⭐⭐⭐)

  1. 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
  2. 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
  3. 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式
    也支持可撤销的操作。
  4. 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
    Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象

命令模式的原理类图

image-20230330200950436

  • Invoker:是调用者角色
  • Command:是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
  • Receiver:接受者角色,知道如何实施和执行一个请求相关的操作
  • ConcreteCommand:将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute

代码实现

image-20230330201155038

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package CommandPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 20:26
*/
// 创建命令接口
public interface Command {

// 执行动作(操作)
public void execute();

// 撤销动作(操作)
public void undo();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package CommandPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 20:29
*/
public class LightReceiver {

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
20
21
22
23
24
25
26
package CommandPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 21:28
*/
public class LightOnCommand implements Command{

LightReceiver light;

public LightOnCommand(LightReceiver light) {
this.light = light;
}

@Override
public void execute() {
light.on();
}

@Override
public void undo() {
light.off();
}
}

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
package CommandPattern;

import javafx.scene.effect.Light;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 20:28
*/
public class LightOffCommand implements Command{

// 聚合LightReceiver
LightReceiver light;

public LightOffCommand(LightReceiver light) {
this.light = light;
}

@Override
public void execute() {
// 调用接收者的方法
light.off();
}

@Override
public void undo() {
// 调用接收者的方法
light.on();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package CommandPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 21:31
*/
//没有任何命令,即空执行:用于初始化每个按钮,当调用空命令时,对象什么都不做其实,
// 这样是一种设计模式,可以省掉对空判断
public class NoCommand implements Command{
@Override
public void execute() {

}

@Override
public void undo() {

}
}

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
package CommandPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 21:33
*/
public class RemoteController {

// 开 按钮的命令数组
Command[] onCommands;
Command[] offCommands;

// 执行撤销的命令
Command undoCommand;

// 构造器,完成对按钮的初始化
public RemoteController() {
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0; i < 5; i++) {
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}

//给按钮设置需要的命令
public void setCommand(int no, Command onCommand, Command offCommand) {
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}

// 按下开按钮
public void onButtonWasPushed(int no) { // no 0
// 找到你按下的开的按钮,并调用对应方法
onCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand = onCommands[no];
}

// 按下关按钮
public void offButtonWasPushed(int no) {
// 找到你按下的管的按钮,调用对应方法
offCommands[no].execute();
// 记录这次的操作,用于撤销
undoCommand = offCommands[no];
}

// 按下撤销按钮
public void undoButtonWasPushed() {
undoCommand.undo();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package CommandPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 14:26
*/
public class TvReceiver {
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
20
21
22
23
24
25
26
package CommandPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 14:28
*/
public class TvOnCommand implements Command{

private TvReceiver tv;

public TvOnCommand(TvReceiver tv) {
this.tv = tv;
}

@Override
public void execute() {
tv.on();
}

@Override
public void undo() {
tv.off();
}
}

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
package CommandPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 14:29
*/
public class TvOffCommand implements Command {
private TvReceiver tv;

public TvOffCommand(TvReceiver tv) {
this.tv = tv;
}

@Override
public void execute() {
tv.off();
}

@Override
public void undo() {
tv.on();
}
}

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
package CommandPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-30 22:04
*/
public class Client {
public static void main(String[] args) {
// 使用命令设计模式,完成通过遥控器,对电灯的操作

// 创建电灯的对象(接受者)
LightReceiver lightReceiver = new LightReceiver();

// 创建电灯相关的开关命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);

// 需要一个遥控器
RemoteController remoteController = new RemoteController();

// 给遥控器设置命令,比如 no = 0是电灯的开和关的操作
remoteController.setCommand(0, lightOnCommand, lightOffCommand);

System.out.println("---------按下灯的开按钮---------");
remoteController.onButtonWasPushed(0);
System.out.println("---------按下灯的关按钮---------");
remoteController.offButtonWasPushed(0);
System.out.println("---------按下灯的撤销按钮---------");
remoteController.undoButtonWasPushed();

System.out.println("=======================================");
// 创建电视机对象
TvReceiver tvReceiver = new TvReceiver();

// 创建电视机相关的开关命令
TvOnCommand tvOnCommand = new TvOnCommand(tvReceiver);
TvOffCommand tvOffCommand = new TvOffCommand(tvReceiver);
remoteController.setCommand(1,tvOnCommand,tvOffCommand);
System.out.println("---------按下电视机的开按钮---------");
remoteController.offButtonWasPushed(1);
System.out.println("---------按下电视机的关按钮---------");
remoteController.onButtonWasPushed(1);
System.out.println("---------按下电视机的撤销按钮---------");
remoteController.undoButtonWasPushed();
}
}

命令模式在 Spring框架JdbcTemplate应用的源码分析

image-20230331151135225

  • StatementCallback接口,类似命令接口(Command)
  • class QueryStatementCallback implements StatementCallback<T>, SqIProvider ,匿名内部类,实现了命令接口,同时也充当命令接收者
  • 命令调用者是JdbcTemplate,其中 execute(StatementCallbackaction)方法中,调用action.doInStatement方法. 不同的实现StatementCallback接口的对象,对应不同的doInStatemnt 实现逻辑
  • 另外实现 StatementCallback命令接口的子类还有QueryStatementCallback、

总结

  1. 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了
    纽带桥梁的作用。
  2. 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
  3. 容易实现对请求的撤销和重做
  4. **命令模式不足:**可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
  5. 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
  6. 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-
    反馈机制

访问者模式(⭐)

需求:

完成测评系统需求
将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(评价有不
同的种类,比如成功、失败等)

image-20230331165402759

传统方案的问题分析:

  • 如果系统比较小,还是ok的,但是考虑系统增加越来越多新的功能时,对代码改动较大,违反了ocp原则,不
    利于维护
  • 扩展性不好,比如增加了新的人员类型,或者管理方法,都不好做
  • 引出我们会使用新的设计模式–访问者模式

访问者模式基本介绍

  1. 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
  2. 主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题
  3. 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
  4. 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),同时需要避免让这些操作”污染”这些对象的类,可以选用访问者模式解决

访问者模式的原理类图

image-20230331215953120

  1. Visitor是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit操作
  2. ConcreteVisitor :是一个具体的访问值实现每个有Visitor声明的操作,是每个操作实现的部分.
  3. ObjectStructure能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素
  4. Element定义一个accept方法,接收一个访问者对象
  5. ConcreteElement为具体元素,实现了accept方法

代码实现

image-20230331220508766

image-20230331225319137

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package VisitorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 22:15
*/
public abstract class Person {


// 提供一个方法,让访问者可以访问
public abstract void accept(Action action);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package VisitorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 22:17
*/
public class Man extends Person{

private String name;

public Man(String name) {
this.name = name;
}

public String getName() {
return name;
}

@Override
public void accept(Action action) {
action.getManResult(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package VisitorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 22:17
*/
public class Woman extends Person{

private String name;

public Woman(String name) {
this.name = name;
}

public String getName() {
return name;
}
@Override
public void accept(Action action) {
action.getWomanResult(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package VisitorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 22:12
*/
public abstract class Action {

// 得到男性的评价
public abstract void getManResult(Man man);

// 得到女的评 价
public abstract void getWomanResult(Woman woman);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package VisitorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 22:21
*/
public class Success extends Action{
@Override
public void getManResult(Man man) {
System.out.println(man.getName() + " 给的评价:该歌手成功");
}

@Override
public void getWomanResult(Woman woman) {
System.out.println(woman.getName() + " 给的评价:该歌手成功");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package VisitorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 22:19
*/
public class Fail extends Action{
@Override
public void getManResult(Man man) {
System.out.println(man.getName() + " 给的评价:该歌手失败");
}

@Override
public void getWomanResult(Woman woman) {
System.out.println(woman.getName() + " 给的评价:该歌手失败");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package VisitorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 22:51
*/
public class Wait extends Action{
@Override
public void getManResult(Man man) {
System.out.println(man.getName() + " 给的评价:该歌手待定");
}

@Override
public void getWomanResult(Woman woman) {
System.out.println(woman.getName() + " 给的评价:该歌手待定");
}
}
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
package VisitorPattern;

import java.util.LinkedList;
import java.util.List;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 22:22
*/
public class ObjectStructure {
// 维护了一个集合
private List<Person> persons = new LinkedList<>();

//增加到list
public void attach(Person p) {
persons.add(p);
}
// 移除
public void detach(Person p) {
persons.remove(p);
}

// 显示测评情况
public void display(Action action) {
for (Person p : persons) {
p.accept(action);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package VisitorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-03-31 22:28
*/
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();

objectStructure.attach(new Man("张三"));
objectStructure.attach(new Woman("李思曼"));

// 成功
Success success = new Success();
objectStructure.display(success);

System.out.println("========================");

Fail fail = new Fail();
objectStructure.display(fail);
}
}

总结

优点

  • 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
  • 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统

缺点

  • 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样造
    成了具体元素变更比较困难
  • 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
  • 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的.

迭代器模式(⭐⭐⭐⭐⭐)

需求:

image-20230401145612924

迭代器模式基本介绍

  1. 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式
  2. 如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类,或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
  3. 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即:不暴露其内部的结构。

迭代器模式原理类图

image-20230401145826257

  1. Iterator :迭代器接口,是系统提供,含义hasNext, next, remove
  2. Concretelterator:具体的迭代器类,管理迭代
  3. Aggregate :一个统一的聚合接口,将客户端和具体聚合解耦
  4. ConcreteAggreage:具体的聚合持有对象集合,并提供一个方法,返回一个迭代器,该迭代器可以正确遍历集合
  5. Client :客户端,通过Iterator和Aggregate依赖子类

代码实现

image-20230401150017101

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
package IteratorPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-01 15:02
*/
// 系
public class Department {
private String name;
private String Desc;

public Department(String name, String desc) {
this.name = name;
Desc = desc;
}

public String getName() {
return name;
}

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

public String getDesc() {
return Desc;
}

public void setDesc(String desc) {
Desc = desc;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package IteratorPattern;

import java.util.Iterator;

/***
* @author dep
* @version 1.0
* @date 2023-04-01 15:16
*/
public interface College {

public String getName();

// 增加系的方法
public void addDepartment(String name, String desc);

// 返回一个迭代器,遍历
public Iterator createIterator();
}

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
package IteratorPattern;

import java.util.Iterator;

/***
* @author dep
* @version 1.0
* @date 2023-04-01 15:03
*/
public class ComputerCollegeIterator implements Iterator {

// 这里我们需要Department是以怎样的方式存放=>数组
Department[] departments;
int position = 0; // 遍历的位置

public ComputerCollegeIterator(Department[] departments) {
this.departments = departments;
}

// 判断是否还有下一个元素
@Override
public boolean hasNext() {
if (position >= departments.length || departments[position] == null) {
return false;
} else {
return true;
}
}

@Override
public Object next() {
Department department = departments[position];
position += 1;
return department;
}

// 删除的方法,默认空实现
public void remove(){

}
}

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
package IteratorPattern;

import java.util.Iterator;
import java.util.List;

/***
* @author dep
* @version 1.0
* @date 2023-04-01 15:11
*/
public class InfoCollegeIterator implements Iterator {

List<Department> departmentList; // 信息工程学院
int index = -1; // 索引

public InfoCollegeIterator(List<Department> departmentList) {
this.departmentList = departmentList;
}

// 判断list中还有没有下一个元素
@Override
public boolean hasNext() {
if (index >= departmentList.size() - 1) {
return false;
} else {
index += 1;
return true;
}
}

@Override
public Object next() {
return departmentList.get(index);
}
}

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
package IteratorPattern;

import java.util.Iterator;

/***
* @author dep
* @version 1.0
* @date 2023-04-01 15:18
*/
public class ComputerCollege implements College{

Department[] departments;
int numOfDepartment = 0; // 保存当前数组的对象个数

public ComputerCollege() {
departments = new Department[5];
addDepartment("JAVA专业", "JAVA专业");
addDepartment("PHP专业", "PHP专业");
addDepartment("大数据专业", "大数据专业");
addDepartment("人工智能专业", "人工智能专业");
}

@Override
public String getName() {
return "计算机学院";
}

@Override
public void addDepartment(String name, String desc) {
Department department = new Department(name, desc);
departments[numOfDepartment] = department;
numOfDepartment ++;
}

@Override
public Iterator createIterator() {
return new ComputerCollegeIterator(departments);
}
}

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
package IteratorPattern;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/***
* @author dep
* @version 1.0
* @date 2023-04-01 15:25
*/
public class InfoCollege implements College{

List<Department> departmentList;

public InfoCollege() {
departmentList = new ArrayList<>();
addDepartment("信息安全专业", "信息安全专业");
addDepartment("网络安全专业", "网络安全专业");
addDepartment("服务器安全专业", "服务器安全专业");
}

@Override
public String getName() {
return "信息工程学院";
}

@Override
public void addDepartment(String name, String desc) {
Department department = new Department(name, desc);
departmentList.add(department);
}

@Override
public Iterator createIterator() {
return new InfoCollegeIterator(departmentList);
}
}

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
package IteratorPattern;

import java.util.Iterator;
import java.util.List;

/***
* @author dep
* @version 1.0
* @date 2023-04-01 15:28
*/
public class OutPutImpl {
// 学院集合
List<College> collegeList;

public OutPutImpl(List<College> collegeList) {
this.collegeList = collegeList;
}

// 遍历所有学院,然后调用printDepartment输出各个学院的系
public void printCollege() {
// 从collgeList去除所有学院,Java的list已经实现Iterator
Iterator<College> iterator = collegeList.iterator();
while (iterator.hasNext()) {
// 取出一个学院
College college = iterator.next();
System.out.println("=======" + college.getName());
printDepartment(college.createIterator());
}
}

private void printDepartment(Iterator iterator) {
while (iterator.hasNext()) {
Department department = (Department)iterator.next();
System.out.println(department.getName());
}
}
}

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
package IteratorPattern;

import java.util.ArrayList;

/***
* @author dep
* @version 1.0
* @date 2023-04-01 15:34
*/
public class Client {
public static void main(String[] args) {
// 创建学院
ArrayList<College> collegeList = new ArrayList<College>();

ComputerCollege computerCollege = new ComputerCollege();
InfoCollege infoCollege = new InfoCollege();

collegeList.add(computerCollege);
collegeList.add(infoCollege);

OutPutImpl outPut = new OutPutImpl(collegeList);
outPut.printCollege();

}
}

迭代器模式在JDK-ArrayList集合应用的源码分析

JDK的ArrayList集合中就使用了迭代器模式

image-20230401155945584

image-20230401160024940

  • 内部类Itr充当具体实现迭代器Iterator的类,作为ArrayList内部类
  • List 就是充当了聚合接口,含有一个iterator()方法,返回一个迭代器对象
  • ArrayList是实现聚合接口List的子类,实现了iterator()
  • Iterator接口系统提供
  • 迭代器模式解决了不同集合(ArrayList ,LinkedList)统一遍历问题

总结

优点

  1. 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
  2. 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
  3. 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。
  4. 当要展示一组相似对象,或者遍历一组相同对象时使用,适合使用迭代器模式

缺点

​ 1. 每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类

观察者模式(⭐⭐⭐⭐⭐)

需求:

  1. 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)
  2. 需要设计开放型API,便于其他第三方也能接入气象站获取数据
  3. 提供温度、气压和湿度的接口
  4. 测量数据更新时,要能实时的通知给第三方

image-20230401163358210

image-20230401163411170

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
package ObserverPattern.tradition;

/***
* @author dep
* @version 1.0
* @date 2023-04-01 16:38
*/
// 显示当前天气情况
public class CurrentConditions {
// 温度,气压,湿度
private float temperature;
private float pressure;
private float humidity;

//更新天气情况,是由WeatherData来调用,我使用推送模式
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}

// 显示
public void display() {
System.out.println("***Today mTemperature: " + temperature + "***");
System.out.println("***Today mPressure: " + pressure+"***");
System.out.println("***Today mHumidity: " + humidity + "***");
}

}
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
package ObserverPattern.tradition;

/**
* 类是核心
* 1.包含最新的天气情况信息
* 2.含有CurrentConditions对象
* 3.当数据有更新时,就主动的调用CurrentConditions对象update方法(含display),
* 这样他们(接入方)就看到最新的信息
*/
public class WeatherData {
private float temperatrue;
private float pressure;
private float humidity;
private CurrentConditions currentConditions;

public WeatherData(CurrentConditions currentConditions) {
this.currentConditions = currentConditions;
}

public float getTemperature() {
return temperatrue;
}

public float getPressure() {
return pressure;
}

public float getHumidity() {
return humidity;
}

// 调用接入方的update
public void dataChange() {
currentConditions.update(getTemperature(), getPressure(), getHumidity());
}

// 当数据有更新时,就调用setData
public void setData(float temperature, float pressure, float humidity) {
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 调用dataChange,将最新的信息推送给接入方currentConditions
dataChange();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package ObserverPattern.tradition;

public class Client {
public static void main(String[] args) {
//创建接入方currentConditions
CurrentConditions currentConditions = new CurrentConditions();
//创建WeatherData并将接入方currentConditions 传递到WeatherData中
WeatherData weatherData = new WeatherData(currentConditions);


weatherData.setData(30, 150, 40);

System.out.println("============天气发生变化=============");
weatherData.setData(40, 160, 20);


}
}

问题分析:

  • 其他第三方接入气象站获取数据的问题

  • 无法在运行时动态的添加第三方(新浪网站)

  • 违反ocp原则=>观察者模式
    // 在WeatherData中,当增加一个第三方,都需要创建一个对应的第三方的公告板对象,并加入到dataChange,不利于维护,也不是动态加入

public void dataChange() { currentConditions.update(getTemperature(), getPressure(), getHumidity()); }

观察者模式原理

  1. 观察者模式类似订牛奶业务
  2. 奶站/气象局: Subject
  3. 用户/第三方网站:Observer

Subject:登记注册、移除和通知

  1. registerObserver 注册
  2. removeObserver 移除
  3. notifyObservers()通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定

Observer:接收输入

观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为Observer,Subject
通知Observer变化,比如这里的奶站是Subject,是1的一方。用户时Observer,是多的一方。

代码实现

image-20230402145809008

1
2
3
4
5
6
7
8
9
10
11
12
13
package ObserverPattern.improve;

/***
* @author dep
* @version 1.0
* @date 2023-04-02 14:59
*/
// 观察者接口,有观察者来实现
public interface Observer {

public void update(float temperature, float pressure,float humidity);

}
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
package ObserverPattern.improve;

/***
* @author dep
* @version 1.0
* @date 2023-04-02 15:01
*/
public class CurrentConditions implements Observer{
private float temperature;
private float pressure;
private float humidity;

// 更新天气情况,是由WeatherData来调用,我使用推送模式
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}

// 显示
public void display() {
System.out.println("***Today mTemperature: " + temperature + "***");
System.out.println("***Today mPressure: " + pressure+"***");
System.out.println("***Today mHumidity: " + humidity + "***");
}
}

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
package ObserverPattern.improve;

/***
* @author dep
* @version 1.0
* @date 2023-04-02 15:24
*/
public class BaiduSite implements Observer{

private float temperature;
private float pressure;
private float humidity;
// 更新天气情况,是由WeatherData来调用,我使用推送模式
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
display();
}

// 显示
public void display() {
System.out.println("===百度网站===");
System.out.println("***百度网站气温:" +temperature + "***");
System.out. println("***百度网站气压: "+ pressure + "***");
System.out.println("***百度网站湿度:"+ humidity + "***");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package ObserverPattern.improve;

/***
* @author dep
* @version 1.0
* @date 2023-04-02 15:04
*/
// 接口,让 WeatherData来实现
public interface Subject {

public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();

}

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
package ObserverPattern.improve;

import java.util.ArrayList;

/***
* @author dep
* @version 1.0
* @date 2023-04-02 15:06
*/
public class WeatherData implements Subject{

private float temperature;

private float pressure;

private float humidity;

// 观察者集合
private ArrayList<Observer> observes;

// 加入新的第三方
public WeatherData() {
observes = new ArrayList<>();
}

public void dataChange() {
// 调用接入方的update
notifyObservers();
}

// 当数据有更新时,就调用setData
public void setData(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 调用dataChange,将最新的信息 推送给 接入方 currentConditions
dataChange();
}


// 注册一个观察者
@Override
public void registerObserver(Observer o) {
observes.add(o);
}

// 移除一个观察者
@Override
public void removeObserver(Observer o) {
if (observes.contains(o)) {
observes.remove(o);
}
}

// 遍历所有的观察者,并通知
@Override
public void notifyObservers() {
for (int i = 0; i < observes.size(); i++) {
observes.get(i).update(this.temperature, this.pressure, this.humidity);
}
}

public float getTemperature() {
return temperature;
}

public float getPressure() {
return pressure;
}

public float getHumidity() {
return humidity;
}
}

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
package ObserverPattern.improve;

/***
* @author dep
* @version 1.0
* @date 2023-04-02 15:25
*/
public class Client {
public static void main(String[] args) {
// 创建一个WeatherData
WeatherData weatherData = new WeatherData();

// 创建观察者
CurrentConditions currentConditions = new CurrentConditions();
BaiduSite baiduSite = new BaiduSite();

// 注册到 weatherData
weatherData.registerObserver(currentConditions);
weatherData.registerObserver(baiduSite);

System.out.println("通知各个注册者,看看信息");
weatherData.setData(15f,40f,30f);

weatherData.removeObserver(currentConditions);

weatherData.setData(10f,20f,30f);
}
}

观察者模式的好处

  1. 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除和通知。
  2. 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp 原则。

观察者模式在Jdk应用的源码分析

  1. Jdk 的Observable类就使用了观察者模式
  2. 代码分析+模式角色分析

image-20230402155541125

  1. Observable的作用和地位等价于我们前面讲过Subject

  2. Observable 是类,不是接口,类中已经实现了核心的方法 ,即管理Observer的方法 add..delete ..

  3. notify…Observer 的作用和地位等价于我们前面讲过的Observer,有update

  4. Observable和 Observer的使用方法和前面讲过的一样,只是Observable是类,通过继承来实现观察者模式

中介者模式(⭐⭐)

定义一个对象来封装一系列对象的交互。中介者模式使个对象之间不需要显式地相互引用,从而使其耦合松散,而其用户可以独立的改变他们之间的交互。

需求:

智能家庭包括各种设备,闹钟、咖啡机、电视机、窗帘等
主人要看电视时,各个设备可以协同工作,自动完成看电视的准备工作,比如流程为:闹铃响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放

image-20230403145553349

  1. 当各电器对象有多种状态改变时,相互之间的调用关系会比较复杂
  2. 各个电器对象彼此联系,你中有我,我中有你,不利于松耦合.
  3. 各个电器对象之间所传递的消息(参数),容易混乱
  4. 当系统增加一个新的电器对象时,或者执行流程改变时,代码的可维护性、扩展性都不理想――考虑中介者模

中介者模式基本介绍

  1. 中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地
    相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
  2. 中介者模式属于行为型模式,使代码易于维护
  3. 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中介者,在前后端交互时起到了中间人的作用

中介者模式原理类图

image-20230403150144390

  1. Mediator 就是抽象中介者,定义了同事对象到中介者对象的接口
  2. Colleague是抽象同事类
  3. ConcreteMediator具体的中介者对象,实现抽象方法,他需要知道所有的具体的同事类,即以一个集合来管理
    HashMap,并接受某个同事对象消息,完成相应的任务
  4. ConcreteColleague具体的同事类,会有很多,每个同事只知道自己的行为,而不了解其他同事类的行为(方法),但是他们都依赖中介者对象

代码实现

image-20230403150342136

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package MediatorPattern.smarthouse;

// 同事抽象类
public abstract class Colleague {
private Mediator mediator;
public String name;

public Colleague(Mediator mediator, String name) {

this.mediator = mediator;
this.name = name;

}

public Mediator GetMediator() {
return this.mediator;
}

public abstract void SendMessage(int stateChange);
}
1
2
3
4
5
6
7
8
9
10
11
12
package MediatorPattern.smarthouse;

public abstract class Mediator {
//将给中介者对象,加入到集合中
public abstract void Register(String colleagueName, Colleague colleague);

//接收消息, 具体的同事对象发出
public abstract void GetMessage(int stateChange, String colleagueName);

public abstract void SendMessage();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package MediatorPattern.smarthouse;

public class TV extends Colleague {

public TV(Mediator mediator, String name) {
super(mediator, name);
mediator.Register(name, this);
}

@Override
public void SendMessage(int stateChange) {
this.GetMediator().GetMessage(stateChange, this.name);
}

public void StartTv() {
System.out.println("It's time to StartTv!");
}

public void StopTv() {
System.out.println("StopTv!");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package MediatorPattern.smarthouse;

public class Curtains extends Colleague {

public Curtains(Mediator mediator, String name) {
super(mediator, name);
mediator.Register(name, this);
}

@Override
public void SendMessage(int stateChange) {
this.GetMediator().GetMessage(stateChange, this.name);
}

public void UpCurtains() {
System.out.println("I am holding Up Curtains!");
}

}

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
package MediatorPattern.smarthouse;

public class CoffeeMachine extends Colleague {

public CoffeeMachine(Mediator mediator, String name) {
super(mediator, name);
mediator.Register(name, this);
}

@Override
public void SendMessage(int stateChange) {
this.GetMediator().GetMessage(stateChange, this.name);
}

public void StartCoffee() {
System.out.println("It's time to startcoffee!");
}

public void FinishCoffee() {

System.out.println("After 5 minutes!");
System.out.println("Coffee is ok!");
SendMessage(0);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package MediatorPattern.smarthouse;

//具体的同事类
public class Alarm extends Colleague {

//构造器
public Alarm(Mediator mediator, String name) {
super(mediator, name);
//在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象中[集合]
mediator.Register(name, this);
}

public void SendAlarm(int stateChange) {
SendMessage(stateChange);
}

@Override
public void SendMessage(int stateChange) {
//调用的中介者对象的getMessage
this.GetMediator().GetMessage(stateChange, this.name);
}

}

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
package MediatorPattern.smarthouse;

import java.util.HashMap;

//具体的中介者类
public class ConcreteMediator extends Mediator {
//集合,放入所有的同事对象
private HashMap<String, Colleague> colleagueMap;
private HashMap<String, String> interMap;

public ConcreteMediator() {
colleagueMap = new HashMap<String, Colleague>();
interMap = new HashMap<String, String>();
}

@Override
public void Register(String colleagueName, Colleague colleague) {

colleagueMap.put(colleagueName, colleague);

if (colleague instanceof Alarm) {
interMap.put("Alarm", colleagueName);
} else if (colleague instanceof CoffeeMachine) {
interMap.put("CoffeeMachine", colleagueName);
} else if (colleague instanceof TV) {
interMap.put("TV", colleagueName);
} else if (colleague instanceof Curtains) {
interMap.put("Curtains", colleagueName);
}

}

//具体中介者的核心方法
//1. 根据得到消息,完成对应任务
//2. 中介者在这个方法,协调各个具体的同事对象,完成任务
@Override
public void GetMessage(int stateChange, String colleagueName) {

//处理闹钟发出的消息
if (colleagueMap.get(colleagueName) instanceof Alarm) {
if (stateChange == 0) {
((CoffeeMachine) (colleagueMap.get(interMap
.get("CoffeeMachine")))).StartCoffee();
((TV) (colleagueMap.get(interMap.get("TV")))).StartTv();
} else if (stateChange == 1) {
((TV) (colleagueMap.get(interMap.get("TV")))).StopTv();
}

} else if (colleagueMap.get(colleagueName) instanceof CoffeeMachine) {
((Curtains) (colleagueMap.get(interMap.get("Curtains"))))
.UpCurtains();

} else if (colleagueMap.get(colleagueName) instanceof TV) {//如果TV发现消息

} else if (colleagueMap.get(colleagueName) instanceof Curtains) {
//如果是以窗帘发出的消息,这里处理...
}

}

@Override
public void SendMessage() {

}

}

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
package MediatorPattern.smarthouse;

public class ClientTest {

public static void main(String[] args) {
//创建一个中介者对象
Mediator mediator = new ConcreteMediator();

//创建Alarm 并且加入到 ConcreteMediator 对象的HashMap
Alarm alarm = new Alarm(mediator, "alarm");

//创建了CoffeeMachine 对象,并 且加入到 ConcreteMediator 对象的HashMap
CoffeeMachine coffeeMachine = new CoffeeMachine(mediator,
"coffeeMachine");

//创建 Curtains , 并 且加入到 ConcreteMediator 对象的HashMap
Curtains curtains = new Curtains(mediator, "curtains");
TV tV = new TV(mediator, "TV");

//让闹钟发出消息
alarm.SendAlarm(0);
coffeeMachine.FinishCoffee();
alarm.SendAlarm(1);
}

}

总结

  1. 多个类相互耦合,会形成网状结构,使用中介者模式将网状结构分离为星型结构,进行解耦
  2. 减少类间依赖,降低了耦合,符合迪米特原则
  3. 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
  4. 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意

备忘录模式(⭐⭐)

需求:

游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态。

image-20230403155404429

  1. 一个对象,就对应一个保存对象状态的对象,这样当我们游戏的对象很多时,不利于管理,开销也很大.
  2. 传统的方式是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就暴露了对象内部的细节
  3. 解决方案:→备忘录模式

备忘录模式基本介绍

  • 备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态

  • 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作

  • 备忘录模式属于行为型模式

备忘录模式的原理类图

image-20230403155903796

  1. originator:对象(需要保存状态的对象)
  2. Memento :备忘录对象,负责保存好记录,即 Originator内部状态
  3. Caretaker:守护者对象,负责保存多个备忘录对象,使用集合管理,提高效率
  4. 说明:如果希望保存多个originator对象的不同时间的状态,也可以,只需要要 HashMap<String,集合>

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Memento;

/***
* @author dep
* @version 1.0
* @date 2023-04-03 16:08
*/
// 备忘录对象(负责保存好记录)
public class Memento {

private String state;

// 构造器
public Memento(String state) {
this.state = state;
}

public String getState() {
return 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
package Memento;

/***
* @author dep
* @version 1.0
* @date 2023-04-03 16:04
*/
// 对象(需要保存状态的对象)
public class Originator {

private String state; // 状态信息

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

// 编写一个方法,可以保存一个状态对象Memento
public Memento saveStateMemento() {
return new Memento(state);
}

// 通过备忘录对象,恢复状态
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}

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
package Memento;

import java.util.ArrayList;
import java.util.List;

/***
* @author dep
* @version 1.0
* @date 2023-04-03 16:13
*/
// 守护者对象(负责保存多个备忘录对象)
public class Caretaker {

// 在List集合中会有很多的备忘录对象
private List<Memento> mementoList = new ArrayList<Memento>();

public void add(Memento memento) {
mementoList.add(memento);
}

// 获取到第index个Originator的备忘录对象(即保存状态)
public Memento get(int index) {
return mementoList.get(index);
}
}

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
package Memento;

/***
* @author dep
* @version 1.0
* @date 2023-04-03 16:17
*/
public class Client {
public static void main(String[] args) {
// 创建一个需要保存状态的对象
Originator originator = new Originator();
// 创建一个守护者对象(负责保存备忘录对象)
Caretaker caretaker = new Caretaker();

originator.setState("状态#1 攻击力100");
// 保存了当前的状态
caretaker.add(originator.saveStateMemento());

originator.setState("状态#2 攻击力100");
caretaker.add(originator.saveStateMemento());

originator.setState("状态#3 攻击力50");
caretaker.add(originator.saveStateMemento());

System.out.println("当前的状态是=" + originator.getState());

// 希望得到状态1 ,将originator恢复到状态1
originator.getStateFromMemento(caretaker.get(0));
System.out.println("恢复到状态1,当前的状态是");
System.out.println("当前的状态是=" + originator.getState());
}
}

游戏角色恢复状态实例

游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大战Boss 后攻击力和防御力下降,从备忘录对象恢复到大战前的状态

image-20230403163303318

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
package Memento.game;

/***
* @author dep
* @version 1.0
* @date 2023-04-03 16:34
*/
// 备忘录对象
public class Memento {

// 攻击力
private int vit;
// 防御力
private int def;

public Memento(int vit, int def) {
this.vit = vit;
this.def = def;
}

public int getVit() {
return vit;
}

public void setVit(int vit) {
this.vit = vit;
}

public int getDef() {
return def;
}

public void setDef(int def) {
this.def = def;
}
}

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
package Memento.game;

/***
* @author dep
* @version 1.0
* @date 2023-04-03 16:33
*/
public class GameRole {
private int vit;
private int def;

// 创建Memento,即根据当前的状态得到Memento
public Memento createMemento() {
return new Memento(vit, def);
}

// 从备忘录对象,恢复GameRole 的状态
public void recoverGameRoleFromMemento(Memento memento) {
this.vit = memento.getVit();
this.def = memento.getDef();
}

// 显示当前游戏角色的状态
public void display() {
System.out.println("游戏角色当前的攻击力:" + this.vit + "防御力: " + this.def);
}

public void setVit(int vit) {
this.vit = vit;
}

public void setDef(int def) {
this.def = def;
}
}
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
package Memento.game;

import java.util.ArrayList;
import java.util.HashMap;

/***
* @author dep
* @version 1.0
* @date 2023-04-03 16:38
*/
public class Caretaker {

// 如果只保存一次状态
private Memento memento;
// 对GameRole保存多次状态
private ArrayList<Memento> mementos;

// 对多个游戏角色保存多个状态
private HashMap<String, ArrayList<Memento>> rolesMementos;

public Memento getMemento() {
return memento;
}

public void setMemento(Memento memento) {
this.memento = memento;
}
}

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
package Memento.game;

/***
* @author dep
* @version 1.0
* @date 2023-04-03 16:43
*/
public class Client {

public static void main(String[] args) {
// TODO Auto-generated method stub
//创建游戏角色
GameRole gameRole = new GameRole();
gameRole.setVit(100);
gameRole.setDef(100);

System.out.println("和boss大战前的状态");
gameRole.display();

//把当前状态保存caretaker
Caretaker caretaker = new Caretaker();
caretaker.setMemento(gameRole.createMemento());

System.out.println("和boss大战~~~");
gameRole.setDef(30);
gameRole.setVit(30);

gameRole.display();

System.out.println("大战后,使用备忘录对象恢复到站前");

gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
System.out.println("恢复后的状态");
gameRole.display();
}

}

总结

  1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
  2. 实现了信息的封装,使得用户不需要关心状态的保存细节
  3. 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存,
    这个需要注意
  4. 适用的应用场景:1、后悔药。2、打游戏时的存档。3、Windows里的 ctri+z。4、E中的后退。4、数
    据库的事务管理
  5. 为了节约内存,备忘录模式可以和原型模式配合使用

解释器模式(⭐)

需求:

  1. 先输入表达式的形式,比如a+b+c-d+e,要求表达式的字母不能重复
  2. 在分别输入 a ,b, c, d,e 的值
  3. 最后求出结果:如图

image-20230404152918192

传统方案解决四则运算问题分析:

  1. 编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果
  2. 问题分析:如果加入新的运算符,比如*/(等等,不利于扩展,另外让一个方法来解析会造成程序结构混乱,
    不够清晰.
  3. 解决方案:可以考虑使用解释器模式,即:表达式>解释器(可以有多种)→>结果

解释器模式基本基本介绍

  1. 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
  2. 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
  3. 应用场景
    -应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树-一些重复出现的问题可以用一种简单的语言来表达-一个简单语法需要解释的场景
  4. 这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等

解释器模式的原理类图

image-20230404153929829

  1. Context:是环境角色,含有解释器之外的全局信息.
  2. AbstractExpression:抽象表达式,声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享
  3. TerminalExpression:为终结符表达式,实现与文法中的终结符相关的解释操作
  4. NonTermialExpression:为非终结符表达式,为文法中的非终结符实现解释操作.
  5. 说明:输入Context he TerminalExpression信息通过Client输入即可

代码实现

image-20230404155534202

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Interpreter;

import java.util.HashMap;

/***
* @author dep
* @version 1.0
* @date 2023-04-04 16:00
*/
// 抽象类表达式,通过HashMap键值对,可以获取到变量的值
public abstract class Expression {
// a+b-c
// 解释公式和数值,key就是公布(表达式)参数[a,b,c],value就是具体指
// HashMap {a = 10, b = 20}
public abstract int interpreter(HashMap<String, Integer>var);
}

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
package Interpreter;

import java.util.HashMap;

/***
* @author dep
* @version 1.0
* @date 2023-04-04 20:01
*/
// 变量的解释器
public class VarExpression extends Expression {

private String key; // key = a, key = b, key = c;

public VarExpression(String key) {
this.key = key;
}

// var 就是{a = 10, b = 20}
// interpreter 根据变量名称,返回对应值
@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}

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
package Interpreter;

import java.util.HashMap;

/***
* @author dep
* @version 1.0
* @date 2023-04-04 19:03
*/
// 抽象运算符号解析器 这里,每个运算符号,都只和自己左右两个数字有关
// 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类
public class SymbolExpression extends Expression{

protected Expression left;
protected Expression right;

public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}

// 因为SymbolExpression是让其子类来实现,因此 interpreter是一个默认实现
@Override
public int interpreter(HashMap<String, Integer> var) {
return 0;
}
}

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
package Interpreter;

import java.util.HashMap;

/***
* @author dep
* @version 1.0
* @date 2023-04-04 20:08
*/
public class AddExpression extends SymbolExpression{
public AddExpression(Expression left, Expression right) {
super(left, right);
}

// 处理相加
// var仍然是{a = 10, b = 20}
// 一会我们debug 源码,就ok

@Override
public int interpreter(HashMap<String, Integer> var) {
// super.left.interpreter(var) :返回left表达式对应的值a = 10
// super.right.interpreter(var):返回right表达式对应值b = 20

return super.left.interpreter(var) + super.right.interpreter(var);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Interpreter;

import java.util.HashMap;

/***
* @author dep
* @version 1.0
* @date 2023-04-04 20:05
*/
public class SubExpression extends SymbolExpression{

public SubExpression(Expression left, Expression right) {
super(left, right);
}

// 求出left和right表达式相减后的结果
@Override
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}

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
package Interpreter;

import java.util.HashMap;
import java.util.Stack;

/***
* @author dep
* @version 1.0
* @date 2023-04-04 16:10
*/
public class Calculator {

// 定义表达式
private Expression expression;

// 构造函数传参,并解析
public Calculator(String expStr) { // expStr = a + b
// 安排运算先后顺序
Stack<Expression> stack = new Stack<>();
// 表达式拆分成字符数组
char[] charArray = expStr.toCharArray(); // [a,+,b]

Expression left = null;
Expression right = null;
// 遍历我们的字符数组,即遍历[a,b]
// 针对不同的情况,做处理
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]) {
case '+':
left = stack.pop(); // stack取出left =>"a"
right = new VarExpression(String.valueOf(charArray[++i])); // 取出右表达式"b"
stack.push(new AddExpression(left, right));
break;
case '-':
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default:
// 如果是一个Var就创建要给VarExpression对象,并push 到 stack
stack.push(new VarExpression(String.valueOf(charArray[i])));
break;
}
}
// 当遍历完整个char Array数组后,stack 就得到最后Expression
this.expression = stack.pop();
}
public int run(HashMap<String, Integer> var) {
// 最后将表达式a+b和 var = {a=10,b=20}
// 然后传递给expression的 interpreter进行解释执行
return this.expression.interpreter(var);
}
}

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
package Interpreter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;

/***
* @author dep
* @version 1.0
* @date 2023-04-04 20:19
*/
public class ClientTest {

public static void main(String[] args) throws IOException {
String expStr = getExpStr(); // a+b
HashMap<String, Integer> var = getValue(expStr);// var {a=10, b=20}
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
}

// 获得表达式
public static String getExpStr() throws IOException {
System.out.print("请输入表达式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}

// 获得值映射
public static HashMap<String, Integer> getValue(String expStr) throws IOException {
HashMap<String, Integer> map = new HashMap<>();

for (char ch : expStr.toCharArray()) {
if (ch != '+' && ch != '-') {
if (!map.containsKey(String.valueOf(ch))) {
System.out.print("请输入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}

return map;
}
}

解释器模式在Spring框架应用的源码剖析

  1. Spring框架中 SpelExpressionParser就使用到解释器模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package Spring;

import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;

/***
* @author dep
* @version 1.0
* @date 2023-04-04 20:51
*/
public class IteratorPattern {
public static void main(String[] args) {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("10*(2+1) + 66");
int result =(Integer) expression.getValue();
System.out.println(result);
}
}

image-20230404204912693

image-20230404204931071

总结

  1. 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
  2. 应用场景:编译器、运算表达式计算、正则表达式、机器人等
  3. 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低.

状态模式(⭐⭐⭐)

需求:

请编写程序完成APP抽奖活动具体要求如下:

  1. 假如每参加一次这个活动要扣除用户50积分,中奖概率是10%
  2. 奖品数量固定,抽完就不能抽奖
  3. 活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完
  4. 活动的四个状态转换关系图(右图)

image-20230405143748234

状态模式基本介绍

  1. 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态
    和行为是一一对应的,状态之间可以相互转换
  2. 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类

状态模式原理类图

image-20230405143918988

  1. Context类为环境角色,用于维护State实例,这个实例定义当前状态
  2. State是抽象状态角色,定义一个接口封装与Context 的一个特点接口相关行为
  3. ConcreteState具体的状态角色,每个子类实现一个与Context的一个状态相关行为

代码实现

定义出一个接口叫状态接口,每个状态都实现它。

接口有扣除积分方法、抽奖方法、发放奖品方法

image-20230405144216664

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package StatePattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-05 14:43
*/
// 状态抽象类
public abstract class State {

// 扣除积分-50
public abstract void deductMoney();

// 是否抽中奖品
public abstract boolean raffle();

// 发放奖品
public abstract void dispensePrize();

}

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
package StatePattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-05 14:47
*/
// 抽奖活动
public class RaffleActivity {

// state 表示活动当前的状态,是变化
State state = null;

// 奖品数量
int count = 0;

// 四个属性,表示始终状态
State noRaffleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);

State dispenseState = new DispenseState(this);
State dispenseOutState = new DispenseOutState(this);

// 构造器
// 1. 初始化当前的状态为 noRaffleState(既不能抽奖的状态)
// 2.初始化奖品的数量
public RaffleActivity(int count) {
this.state = getNoRaffleState();
this.count = count;
}

//扣分,调用当前的状态deductMoney
public void debuctMoney() {
state.deductMoney();
}

// 抽奖
public void raffle() {
// 如果当前的状态是抽奖成功
if (state.raffle()) {
// 领取奖品
state.dispensePrize();
}
}

public State getState() {
return state;
}

public void setState(State state) {
this.state = state;
}
// 每领取一次奖品,count--
public int getCount() {
int curCount = count;
count--;
return curCount;
}

public void setCount(int count) {
this.count = count;
}

public State getNoRaffleState() {
return noRaffleState;
}

public void setNoRaffleState(State noRaffleState) {
this.noRaffleState = noRaffleState;
}

public State getCanRaffleState() {
return canRaffleState;
}

public void setCanRaffleState(State canRaffleState) {
this.canRaffleState = canRaffleState;
}

public State getDispenseState() {
return dispenseState;
}

public void setDispenseState(State dispenseState) {
this.dispenseState = dispenseState;
}

public State getDispenseOutState() {
return dispenseOutState;
}

public void setDispenseOutState(State dispenseOutState) {
this.dispenseOutState = dispenseOutState;
}
}

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
package StatePattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-05 14:46
*/
// 不能抽奖状态
public class NoRaffleState extends State{

// 初始话时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;

public NoRaffleState(RaffleActivity activity) {
this.activity = activity;
}

// 当前状态可以扣除积分,扣除后,将状态设置成可以抽奖状态
@Override
public void deductMoney() {
System.out.println("扣除50积分成功,您可以抽奖了");
activity.setState(activity.getCanRaffleState());
}

// 当前状态不能抽奖
@Override
public boolean raffle() {
System.out.println("扣除50积分才能抽奖喔!");
return false;
}

// 当前状态不能发奖品
@Override
public void dispensePrize() {
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
package StatePattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-05 15:05
*/
public class DispenseState extends State{

// 初始化时传入活动引用,发放奖品后改变其状态
RaffleActivity activity;

public DispenseState(RaffleActivity activity) {
this.activity = activity;
}

@Override
public void deductMoney() {
System.out.println("不能扣除积分");
}

@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}

// 发放奖品
@Override
public void dispensePrize() {
if (activity.getCount() > 0) {
System.out.println("恭喜中奖了");
// 改变状态不能抽奖
activity.setState(activity.getNoRaffleState());
} else {
System.out.println("很遗憾,奖品发送完了");
// 改变状态为奖品发送完毕,后面我们就不可以抽奖
activity.setState(activity.getDispenseState());
System.out.println("抽奖活动结束");
System.exit(0);
}
}
}

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
package StatePattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-05 15:01
*/
// 奖品发放完毕状态
// 说明,当我们activity改变成 DispenseOutState,抽奖活动结束

public class DispenseOutState extends State{

// 初始化时传入活动引用
RaffleActivity activity;

public DispenseOutState(RaffleActivity activity) {
this.activity = activity;
}

@Override
public void deductMoney() {
System.out.println("奖品发送完了,请下次再长假");
}

@Override
public boolean raffle() {
System.out.println("奖品发送完了,请下次再参加");
return false;
}

@Override
public void dispensePrize() {
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
46
47
48
49
50
package StatePattern;

import java.util.Random;

/***
* @author dep
* @version 1.0
* @date 2023-04-05 14:54
*/
// 可以抽奖的状态
public class CanRaffleState extends State{

RaffleActivity activity;

public CanRaffleState(RaffleActivity activity) {
this.activity = activity;
}

// 已经扣除了积分,不能再扣
@Override
public void deductMoney() {
System.out.println("已经扣取过了积分");
}

// 可以抽奖,抽完奖后,根据实际情况,改成新的状态
@Override
public boolean raffle() {
System.out.println("正在抽奖,请稍等");
Random random = new Random();
int num = random.nextInt(10);
// 10%的中奖机会
if (num == 0) {
// 改变活动状态为发放奖品 context
activity.setState(activity.getDispenseState());
return true;
} else {
System.out.println("很遗憾没有抽中奖品!");
// 改变状态为不能抽奖
activity.setState(activity.getNoRaffleState());
return false;
}
}

// 不能发放奖品
@Override
public void dispensePrize() {
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
package StatePattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-05 15:47
*/

// 状态模式测试类
public class ClientTest {

public static void main(String[] args) {
// 创建活动对象,奖品有1个奖品
RaffleActivity activity = new RaffleActivity(1);

// 我们连续抽300次奖
for (int i = 0; i < 30; i++) {
System.out.println("--------第" + (i + 1) + "次抽奖----------");
// 参加抽奖,第一步点击扣除积分
activity.debuctMoney();

// 第二步抽奖
activity.raffle();
}
}

}

状态模式在实际项目-借贷平台

借贷平台的订单,有审核-发布-抢单等等步骤,随着操作的不同,会改变订单的状态,项目中的这个模块实现就会使用到状态模式
通常通过if/else判断订单的状态,从而实现不同的逻辑,伪代码如下

总结

  1. 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
  2. 方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错
  3. 符合“开闭原则”。容易增删状态
  4. 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
  5. 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式

策略模式(⭐⭐⭐⭐)

定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法可以独立于使用它的客户而变化。

需求:

  1. 有各种鸭子(比如野鸭、北京鸭、水鸭等,鸭子有各种行为,比如叫、飞行等
  2. 显示鸭子的信息

image-20230406142058076

传统继承方式问题分析:

  1. 其它鸭子,都继承了Duck类,所以fly 让所有子类都会飞了,这是不正确的
  2. 上面说的1的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有
    溢出效应
  3. 为了改进1问题,我们可以通过覆盖fly方法来解决=→覆盖解决
  4. 问题又来了,如果我们有一个玩具鸭子ToyDuck,这样就需要ToyDuck 去覆盖Duck的所有实现的方法=→>解
    决思路-》策略模式(strategy pattern)

策略模式基本介绍

  1. 策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式
    算法的变化独立于使用算法的客户
  2. 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体
    类(定义了策略接口)﹔第三、多用组合/聚合,少用继承(客户通过组合方式使用策略

策略模式的原理类图

image-20230406142523739

从上图可以看到,客户context有成员变量strategy或者其他的策略接口,至于需要使用到哪个策略,我们可以在构造器中指定

代码实现

策略模式:分别封装行为接口,实现算法族,超类里放行为接口对象,在子类里具体设定行为对象。原则就是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者

image-20230406142647473

QuackBehavor和FlyBehavior功能相似,所以就不重复写

1
2
3
4
5
6
7
8
9
10
11
12
13
package StrategyPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 14:30
*/
public interface FlyBehavior {

void fly(); // 子类具体实现

}

1
2
3
4
5
6
7
8
9
10
11
12
package StrategyPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 14:36
*/
public interface QuackBehavior {

void quack(); // 子类实现
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package StrategyPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 14:33
*/
public class NoFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("不会飞翔~~~");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package StrategyPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 14:32
*/
public class GoodFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("飞翔技术高超~~~");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package StrategyPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 14:31
*/
public class BadFlyBehavior implements FlyBehavior{
@Override
public void fly() {
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
package StrategyPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 14:34
*/
public abstract class Duck {

// 属性,策略接口
FlyBehavior flyBehavior;
// 其他属性--策略接口
QuackBehavior quackBehavior;

public Duck() {

}

public abstract void display(); // 显示鸭子信息

public void quack() {
System.out.println("鸭子嘎嘎叫~~~");
}

public void swim() {
System.out.println("鸭子会游泳~~~");
}

public void fly() {
if (flyBehavior != null) {
flyBehavior.fly();
}
}

public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}

public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package StrategyPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 14:45
*/
public class ToyDuck extends Duck{

public ToyDuck() {
flyBehavior = new NoFlyBehavior();
}

@Override
public void display() {
System.out.println("玩具鸭");
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package StrategyPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 14:47
*/
public class WildDuck extends Duck{

public WildDuck() {
flyBehavior = new GoodFlyBehavior();
}

@Override
public void display() {
System.out.println("这是野鸭");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package StrategyPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 14:42
*/
public class PekingDuck extends Duck{

// 假如北京鸭可以飞翔,但是飞翔技术一般
public PekingDuck() {
flyBehavior = new BadFlyBehavior();
}

@Override
public void display() {
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
package StrategyPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 14:49
*/
public class Client {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck();
wildDuck.fly();

ToyDuck toyDuck = new ToyDuck();
toyDuck.fly();

PekingDuck pekingDuck = new PekingDuck();
pekingDuck.fly();

// 动态改变某个对象的行为,北京鸭不能飞
pekingDuck.setFlyBehavior(new NoFlyBehavior());
System.out.println("北京鸭的实际飞行能力");
pekingDuck.fly();
}
}

策略模式在JDK-Arrays 应用的源码分析

image-20230406152156436

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
package JDK_SRC;

import java.util.Arrays;
import java.util.Comparator;

/***
* @author dep
* @version 1.0
* @date 2023-04-06 15:22
*/
public class Strategy {
public static void main(String[] args) {
Integer[] data = {9,1,2,8,4,3};

// 1.实现了Comparator接口(策略接口),匿名类对象new Comparator<Integer>(){..}
// 2.对象new Comparator<Integer>(){..}就是实现了策略接口的对象
// 3. public int compare(Integer o1, Integer o2){}指定具体的处理方式

// public static <T> void sort(T[] a, Comparator<? super T> c) {
// if (c == null) {
// sort(a);
// } else {
// if (Arrays.LegacyMergeSort.userRequested)
// legacyMergeSort(a, c); // 使用策略对象C
// else
// 使用策略对象C
// TimSort.sort(a, 0, a.length, c, null, 0, 0);
// }
// }
Arrays.sort(data, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 >= o2) {
return -1;
} else {
return 1;
}
}
});
System.out.println(Arrays.toString(data));

Integer[] data2 = {19,11,12,18,14,13};
Arrays.sort(data2, (var1, var2) -> {
if (var1.compareTo(var2) > 0) {
return -1;
} else {
return 1;
}
});
System.out.println(Arrays.toString(data2));
}
}

总结

  1. 策略模式的关键是:分析项目中变化部分与不变部分
  2. 策略模式的核心思想是:多用组合/聚合少用继承;用行为类组合,而不是行为的继承。更有弹性
  3. 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)
    即可,避免了使用多重转移语句( if..else if..else)
  4. 提供了可以替换继承关系的办法:策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context 改
    变它,使它易于切换、易于理解、易于扩展
  5. **需要注意的是:**每添加一个策略就要增加一个类,当策略过多是会导致类数目庞

职责链模式(⭐⭐)

需求:

采购员采购教学器材

  1. 如果金额小于等于5000,由教学主任审批(O<=x<=5000)
  2. 如果金额小于等于10000,由院长审批(5000<x<=10000)
  3. 如果金额小于等于30000,由副校长审批(10000<x<=30000)
  4. 如果金额超过30000以上,有校长审批(30000<x)

image-20230407141649186

传统设计存在的问题:

  1. 传统方式是:接收到一个采购请求后,根据采购金额来调用对应的Approver (审批人)完成审批。

  2. 传统方式的问题分析︰客户端这里会使用到分支判断(比如 switch)来对不同的采购请求处理,这样就存在如下问题

    ​ (1)如果各个级别的人员审批金额发生变化,在客户端的也需要变化

    ​ (2)客户端必须明确的知道有多少个审批级别和访问

  3. 这样对一个采购请求进行处理和Approver (审批人)就存在强耦合关系,不利于代码的扩展和维护

职责链模式基本介绍

  1. 职责链模式(Chain of Responsibility Pattern),又叫责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
  2. 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求那么它会把相同的请求传给下一个接收者,依此类推。
  3. 这种类型的设计模式属于行为型模式

职责链模式的原理类图

image-20230407141958781

  1. Handler:抽象的处理者,定义了一个处理请求的接口,同时含义另外Handler
  2. ConcretcHandlerA,B 是具体的处理者,处理它自己负责的请求,可以访问它的后继者(即下一个处理者),如果可以处理当前请求,则处理,否则就将该请求交个后继者去处理,从而形成一个职责链.
  3. Request ,含义很多属性,表示一个请求

代码实现

image-20230407142232770

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
package ChainOfResponsibilityPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-07 14:27
*/
// 请求类
public class PurchaseRequest {

private int type = 0; //请求类型
private float price = 0.0f; // 请求金额
private int id = 0;

// 构造器
public PurchaseRequest(int type, float price, int id) {
this.type = type;
this.price = price;
this.id = id;
}

public int getType() {
return type;
}

public float getPrice() {
return price;
}

public int getId() {
return 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
package ChainOfResponsibilityPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-07 14:24
*/
public abstract class Approver {

Approver approver; // 下一个处理者
String name; // 名字

public Approver(String name) {
this.name = name;
}

// 下一个处理者
public void setApprover(Approver approver) {
this.approver = approver;
}

// 处理审批请求的方法,得到一个请求,处理是子类完成
public abstract void processRequest(PurchaseRequest purchaseRequest);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package ChainOfResponsibilityPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-07 14:34
*/
public class DepartmentApprover extends Approver{
public DepartmentApprover(String name) {
super(name);
}

@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() <= 5000) {
System.out.println("请求编号id=" + purchaseRequest.getId()+" 被 "+ this.name+"处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package ChainOfResponsibilityPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-07 14:30
*/
public class CollegeApprover extends Approver{

public CollegeApprover(String name) {
super(name);
}

@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() > 5000 && purchaseRequest.getPrice() <= 10000) {
System.out.println("请求编号id=" + purchaseRequest.getId()+" 被 "+ this.name+"处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package ChainOfResponsibilityPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-07 14:38
*/
public class ViceSchoolMasterApprover extends Approver{
public ViceSchoolMasterApprover(String name) {
super(name);
}

@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() > 10000 && purchaseRequest.getPrice() <= 30000) {
System.out.println("请求编号id=" + purchaseRequest.getId()+" 被 "+ this.name+"处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package ChainOfResponsibilityPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-07 14:37
*/
public class SchoolMasterApprover extends Approver{

public SchoolMasterApprover(String name) {
super(name);
}

@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() > 30000) {
System.out.println("请求编号id=" + purchaseRequest.getId()+" 被 "+ this.name+"处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}

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
package ChainOfResponsibilityPattern;

/***
* @author dep
* @version 1.0
* @date 2023-04-07 14:40
*/
public class Client {
public static void main(String[] args) {
PurchaseRequest purchaseRequest = new PurchaseRequest(1,30001,1);

// 创建相关的审批人
DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("王院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("李副校长");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("邓校长");

// 需要将各个审批级别的下一个设置好(处理人构成环形)
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
schoolMasterApprover.setApprover(departmentApprover);

departmentApprover.processRequest(purchaseRequest);
viceSchoolMasterApprover.processRequest(purchaseRequest);
}
}

职责链模式在SpringMVC框架应用的源码分析

  1. SpringMVC-HandlerExecutionChain类就使用到职责链模式
  2. SpringMVC请求流程简图

image-20230407155118515

image-20230407155755711

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
package Spring.springmvc;

import org.springframework.web.servlet.DispatcherServlet;

/***
* @author dep
* @version 1.0
* @date 2023-04-07 15:52
*/
public class Responsibility {
public static void main(String[] args) {
// DispatcherServlet
/***
* 说明:
*protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
* ....
* HandlerExecutionChain mappedHandler = null;
* ....
* mappedHandler = this.getHandler(processedRequest); // 获取HandlerExecutionChain对象
* ....
* // 在mappedHandler.applyPreHandle 内部得到啦HandlerInterceptor interceptor
* //调用了拦截器的interceptor.preHandle
* if (!mappedHandler.applyPreHandle(processedRequest, response)) {
* return;
* }
* }
* //说明: mappedHandler.applyPostHandle方法内部获取到拦截器,并调用
* //拦截器的interceptor.postHandle(request, response, this.handler, mv);
* mappedHandler.applyPostHandle(processedRequest, response, mv);
*
*/
}
}

  • springmvc请求的流程图中,执行了拦截器相关方法 interceptor.preHandler 等等在处理SpringMvc请求时,使用到职责链模式还使用到适配器模式
  • HandlerExecutionChain 主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链实现方式;减少职责链本身与处理逻辑之间的耦合,规范了处理流程
  • HandlerExecutionChain 维护了HandlerInterceptor的集合,可以向其中注册相应的拦截器.

总结

  1. 将请求和处理分开,实现解耦,提高系统的灵活性
  2. 简化了对象,使对象不需要知道链的结构
  3. 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在Handler 中设置一个
    最大节点数量,在setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
  4. 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
  5. 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web 中Tomcat对Encoding的处理、拦截器

设计模式
http://example.com/2023/03/01/06.扩展/04.设计模式/01.设计模式/
作者
Deng ErPu
发布于
2023年3月1日
许可协议