0%

7种结构型设计模式

结构型设计模式:关注对象之间的组合方式,以达到构建更大结构的目标,践行“组合优于继承”理念。

结构型设计模式共7种:桥接设计模式,合成设计模式,门面设计模式,享元设计模式,适配器设计模式,装饰器设计模式,代理设计模式。
助记:

  • 装饰器、代理
  • 桥接、合成、门面
  • 适配器、享元

几种易混淆设计模式之间的区分:

  • 装饰器设计模式 VS 代理设计模式:前者是后者的子集

一、桥接设计模式

英文名:Bridge Pattern。

1.1、痛点

业务需求有两个关联的变化维度(大于两个变化维度的处理类似),两个变化维度分别有M和N种可能:

  1. 以继承方式实现,需要M*N个具体子类
  2. 践行“组合优于继承”理念,使用组合方式实现,只需要M+N个具体子类

以代码的形式进行描述即:有两个抽象父类A和B,A中具有一个B的引用,A有M个具体子类,B有N个具体子类。

1.2、实现

示例代码如下[1]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// 抽象父类B:颜色接口
interface Color {
void printColor();
}

// 颜色接口的具体子类
class Red implements Color {
public void printColor() {
System.out.println("print red color");
}
}

// 颜色接口的具体子类
class Yellow implements Color {
public void printColor() {
System.out.println("print yellow color");
}
}

// 颜色接口的具体子类
class Blue implements Color {
public void printColor() {
System.out.println("print blue color");
}
}


// 抽象父类A:形状接口,持有对颜色接口的引用
abstract class Shape {
// 一个对颜色接口的引用
protected Color color;

public Shape(Color color) {
this.color = color;
}

abstract void draw();
}

// 形状接口的具体子类
class Circle extends Shape {
public Circle(Color color) {
super(color);
}

public void draw() {
System.out.print("Drawing a circle. ");
color.printColor();
}
}

// 形状接口的具体子类
class Square extends Shape {
public Square(Color color) {
super(color);
}

public void draw() {
System.out.print("Drawing a square. ");
color.printColor();
}
}

// 形状接口的具体子类
class Rectangle extends Shape {
public Rectangle(Color color) {
super(color);
}

public void draw() {
System.out.print("Drawing a rectangle. ");
color.printColor();
}
}

// 在这个示例中,颜色变化维度有3种可能,形状变化维度有3种可能:
// 1)如果采用继承实现,需要有3*3=9个具体子类
// 2)这里采用桥接设计模式实现,只需要3+3=6个具体子类
public class BridgePatternExample {
public static void main(String[] args) {
Color redColor = new Red();
Color yellowColor = new Yellow();
Color blueColor = new Blue();

Shape redCircle = new Circle(redColor);
Shape yellowRectangle = new Rectangle(yellowColor);
Shape blueSquare = new Square(blueColor);

redCircle.draw();
yellowRectangle.draw();
blueSquare.draw();
}
}

1.3、JDK中的例子

  • 抽象父类java.util.logging.Handlerjava.util.logging.Formatter,在java.util.logging.Handler中持有一个java.util.logging.Formatter引用

二、合成设计模式

英文名:Composite Pattern。

很多时候也被称为“组合设计模式”,但易与“Composition over Inheritance / Composite Reuse Principle”的中文名“组合优于继承”产生混淆,故建议将前者中的“Composite”译为“合成”,而将后者中的“Composition / Composite”译为“组合”。

2.1、痛点

有一组部件,部件之间具有整体-部分的关系,希望以一致的方式处理部分部件整体部件,而不需要进行特殊处理。在该场景中,部分部件和整体部件的共同抽象父类被称为“组件接口”,部分部件被称为“叶子部件”,整体部件被称为“容器部件”

2.2、实现

示例代码如下[1]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 组件接口
interface FileSystemComponent {
void displayInfo();
}

// 叶子部件
class File implements FileSystemComponent {
private String name;

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

public void displayInfo() {
System.out.println("File: " + name);
}
}

// 容器部件
class Directory implements FileSystemComponent {
private String name;
private List<FileSystemComponent> components;

public Directory(String name) {
this.name = name;
components = new ArrayList<>();
}

public void addComponent(FileSystemComponent component) {
components.add(component);
}

public void displayInfo() {
System.out.println("Directory: " + name);
for (FileSystemComponent component : components) {
component.displayInfo();
}
}
}

public class CompositePatternExample {
public static void main(String[] args) {
// 创建文件和文件夹
File file1 = new File("file1.txt");
File file2 = new File("file2.txt");
Directory subDirectory = new Directory("Subdirectory");
subDirectory.addComponent(file1);
subDirectory.addComponent(file2);

Directory rootDirectory = new Directory("Root");
rootDirectory.addComponent(subDirectory);

// 展示文件系统结构
rootDirectory.displayInfo();
}
}

2.3、JDK中的例子

  • java.awt.Component是合成设计模式中的组件接口,java.awt.Container是相应的容器部件,java.awt.Checkboxjava.awt.Labeljava.awt.Button等是相应的叶子部件

三、门面设计模式

英文名:Facade Pattern。

又常被称为“外观设计模式”。

3.1、痛点

提供一个简单易用具有一致性的接口,屏蔽实际实现的复杂性和多样性。在该场景中,有3个角色——Client(业务代码)Facade(门面接口)具体实现Facade的逻辑,3个角色的交互是单向的:Client -> Facade -> 具体实现Facade的逻辑

3.2、实现

3.2.1、示例代码1[1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 子系统:音响
class StereoSystem {
public void turnOn() {
System.out.println("Stereo System is turned on");
}

public void turnOff() {
System.out.println("Stereo System is turned off");
}
}

// 子系统:投影仪
class Projector {
public void turnOn() {
System.out.println("Projector is turned on");
}

public void turnOff() {
System.out.println("Projector is turned off");
}
}

// 子系统:灯光控制
class LightsControl {
public void turnOn() {
System.out.println("Lights are turned on");
}

public void turnOff() {
System.out.println("Lights are turned off");
}
}

// 门面类:家庭影院外观
class HomeTheaterFacade {
private StereoSystem stereo;
private Projector projector;
private LightsControl lights;

public HomeTheaterFacade() {
stereo = new StereoSystem();
projector = new Projector();
lights = new LightsControl();
}

public void watchMovie() {
System.out.println("Getting ready to watch a movie...");
lights.turnOff();
projector.turnOn();
stereo.turnOn();
}

public void endMovie() {
System.out.println("Ending the movie...");
stereo.turnOff();
projector.turnOff();
lights.turnOn();
}
}

// HomeTheaterFacade充当了一个门面类,封装了音响、投影仪和灯光控制等子系统的复杂操作,以便业务代码可以通过简单的调用来完成观影过程
// 这样,业务代码不需要了解各个子系统的具体操作,只需通过门面类的方法来控制整个家庭影院系统的行为
public class FacadePatternExample {
public static void main(String[] args) {
HomeTheaterFacade homeTheater = new HomeTheaterFacade();

// 准备观影
homeTheater.watchMovie();

// 结束观影
homeTheater.endMovie();
}
}

3.2.2、示例代码2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package org.slf4j;

public interface Logger {
String ROOT_LOGGER_NAME = "ROOT";

String getName();

boolean isTraceEnabled();

void trace(String var1);

void trace(String var1, Object var2);

void trace(String var1, Object var2, Object var3);

void trace(String var1, Object... var2);

void trace(String var1, Throwable var2);

boolean isTraceEnabled(Marker var1);

void trace(Marker var1, String var2);

void trace(Marker var1, String var2, Object var3);

void trace(Marker var1, String var2, Object var3, Object var4);

void trace(Marker var1, String var2, Object... var3);

void trace(Marker var1, String var2, Throwable var3);

boolean isDebugEnabled();

void debug(String var1);

void debug(String var1, Object var2);

void debug(String var1, Object var2, Object var3);

void debug(String var1, Object... var2);

void debug(String var1, Throwable var2);

boolean isDebugEnabled(Marker var1);

void debug(Marker var1, String var2);

void debug(Marker var1, String var2, Object var3);

void debug(Marker var1, String var2, Object var3, Object var4);

void debug(Marker var1, String var2, Object... var3);

void debug(Marker var1, String var2, Throwable var3);

boolean isInfoEnabled();

void info(String var1);

void info(String var1, Object var2);

void info(String var1, Object var2, Object var3);

void info(String var1, Object... var2);

void info(String var1, Throwable var2);

boolean isInfoEnabled(Marker var1);

void info(Marker var1, String var2);

void info(Marker var1, String var2, Object var3);

void info(Marker var1, String var2, Object var3, Object var4);

void info(Marker var1, String var2, Object... var3);

void info(Marker var1, String var2, Throwable var3);

boolean isWarnEnabled();

void warn(String var1);

void warn(String var1, Object var2);

void warn(String var1, Object... var2);

void warn(String var1, Object var2, Object var3);

void warn(String var1, Throwable var2);

boolean isWarnEnabled(Marker var1);

void warn(Marker var1, String var2);

void warn(Marker var1, String var2, Object var3);

void warn(Marker var1, String var2, Object var3, Object var4);

void warn(Marker var1, String var2, Object... var3);

void warn(Marker var1, String var2, Throwable var3);

boolean isErrorEnabled();

void error(String var1);

void error(String var1, Object var2);

void error(String var1, Object var2, Object var3);

void error(String var1, Object... var2);

void error(String var1, Throwable var2);

boolean isErrorEnabled(Marker var1);

void error(Marker var1, String var2);

void error(Marker var1, String var2, Object var3);

void error(Marker var1, String var2, Object var3, Object var4);

void error(Marker var1, String var2, Object... var3);

void error(Marker var1, String var2, Throwable var3);
}

以上是门面日志框架SLF4J的日志门面接口,运行时,框架会绑定(内部通过“适配器设计模式”实现)到根据一定策略找到选中的具体日志框架,比如“Log4j”,“Logback”,“JDK Logging”,“Commons Logging”等。
业务代码使用SLF4J调用日志记录接口时的交互方向是:业务代码 -> org.slf4j.Logger日志门面接口 -> 具体绑定到的日志类
使用日志门面接口,得到了一个简单易用具有一致性的日志接口,屏蔽了具体日志实现的多样性。

3.3、JDK中的例子

  • java.sql.DriverManager是一个门面接口,屏蔽了操作数据库驱动器实例的复杂性

四、享元设计模式

英文名:Flyweight Pattern。

4.1、痛点

为节约资源,维护一个实例对象池,当欲根据一定条件获取一个实例对象时,先看池中是否有符合条件的实例对象,存在直接返回池中实例对象,不重新生成;不存在则生成一个新实例对象,放入池中并返回。享元设计模式本质就是常说的用于资源优化的池技术。

4.2、实现

示例代码如下[1]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.util.HashMap;
import java.util.Map;

enum Color {
RED, BLUE, GREEN;
}

class Circle {
private Color color;

public Circle(Color color) {
this.color = color;
}
}

class ShapeFactory {
private static final Map<Color, Circle> circleMap = new HashMap<>();

public static Circle getCircle(Color color) {
Circle circle = circleMap.get(color);

if (circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
}

return circle;
}
}

public class FlyweightPatternExample {

public static void main(String[] args) {

Color[] colors = {Color.RED, Color.GREEN, Color.BLUE};

for (int i = 0; i < 20; i++) {
Color randomColor = colors[(int) (Math.random() * colors.length)];
Circle circle = ShapeFactory.getCircle(randomColor);
}
}
}

4.3、JDK中的例子

  • 线程池
  • 数据库连接池
  • Byte/Short/Integer/Long/Character/Boolean类的valueOf()方法实现中使用了享元设计模式
  • java.lang.String类的intern()方法实现中使用了享元设计模式

五、适配器设计模式

英文名:Adapter Pattern。

5.1、痛点

现有一个类A,业务代码只能使用类B,创建一个类C,它继承自类B,内部持有一个类A的实例引用a,其内部方法的实现主要通过调用a的方法实现。

5.2、实现

示例代码如下[1]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 统一的Shape接口
interface Shape {
void draw(int x, int y, int width, int height);
}

// 已存在的LegacyRectangle类
class LegacyRectangle {
public void display(int x1, int y1, int x2, int y2) {
System.out.println("LegacyRectangle: Point1(" + x1 + ", " + y1 + "), Point2(" + x2 + ", " + y2 + ")");
}
}

// 适配器类,将LegacyRectangle适配到Shape接口上
class RectangleAdapter implements Shape {
private LegacyRectangle legacyRectangle;

public RectangleAdapter(LegacyRectangle legacyRectangle) {
this.legacyRectangle = legacyRectangle;
}

@Override
public void draw(int x, int y, int width, int height) {
int x1 = x;
int y1 = y;
int x2 = x + width;
int y2 = y + height;
legacyRectangle.display(x1, y1, x2, y2);
}
}

// 在这个示例中,LegacyRectangle是已经存在的类,而RectangleAdapter是适配器类,用于将LegacyRectangle适配到Shape接口上
// 业务代码通过使用适配器来画一个矩形,实际上是调用了LegacyRectangle的display方法,但是通过适配器,它符合了Shape接口的标准
public class AdapterPatternExample {
public static void main(String[] args) {
LegacyRectangle legacyRectangle = new LegacyRectangle();
Shape shapeAdapter = new RectangleAdapter(legacyRectangle);

shapeAdapter.draw(10, 20, 50, 30);
}
}

5.3、JDK中的例子

  • 适配器类java.io.InputStreamReader:用于将java.io.InputStream类适配到接口java.io.Reader
  • 适配器类java.io.OutputStreamWriter:用于将java.io.OutputStream类适配到接口java.io.Writer

六、装饰器设计模式

英文名:Decorator Pattern。

6.1、痛点

已有接口A,一个实现接口A的类B,再新建一个实现接口A的类C,作为类B的装饰器类,其内部持有一个类B的实例引用b,其实现方法的形式为简单扩展语句(装饰语句) + 调用b的相应方法,即实现“在复用类B代码基础上,对类B进行浅度扩展”需求。

6.2、实现

示例代码如下[1]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 一个咖啡接口
interface Coffee {
double cost();

String description();
}

// 一个基本的咖啡类
class SimpleCoffee implements Coffee {
@Override
public double cost() {
return 2.0;
}

@Override
public String description() {
return "Simple Coffee";
}
}

// 一个装饰器类
class MilkDecorator implements Coffee {

Coffee decoratedCoffee;

public MilkDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}

@Override
public double cost() {
return decoratedCoffee.cost() + 1.0;
}

@Override
public String description() {
return decoratedCoffee.description() + ", with Milk";
}
}

// 另外一个装饰器类
class SugarDecorator implements Coffee {

Coffee decoratedCoffee;

public SugarDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}

@Override
public double cost() {
return decoratedCoffee.cost() + 0.5;
}

@Override
public String description() {
return decoratedCoffee.description() + ", with Sugar";
}
}

// 在这个示例中,Coffee接口定义了基本的咖啡功能。SimpleCoffee类实现了基本的咖啡
// MilkDecorator和SugarDecorator是两个装饰器类,在原始咖啡上添加新的功能
public class DecoratorPatternExample {
public static void main(String[] args) {
Coffee simpleCoffee = new SimpleCoffee();
System.out.println("Cost: $" + simpleCoffee.cost() + ", Description: " + simpleCoffee.description());

Coffee milkCoffee = new MilkDecorator(simpleCoffee);
System.out.println("Cost: $" + milkCoffee.cost() + ", Description: " + milkCoffee.description());

Coffee sugarMilkCoffee = new SugarDecorator(milkCoffee);
System.out.println("Cost: $" + sugarMilkCoffee.cost() + ", Description: " + sugarMilkCoffee.description());
}
}

6.3、JDK中的例子

  • java.io.BufferedReaderjava.io.Reader进行装饰
  • java.io.BufferedWriterjava.io.Writer进行装饰
  • java.io.BufferedInputStreamjava.io.InputStream进行装饰
  • java.io.BufferedOutputStreamjava.io.OutputStream进行装饰

七、代理设计模式

英文名:Proxy Pattern。

7.1、痛点

已有接口A,一个实现接口A的类B,再新建一个实现接口A的类C,作为类B的代理类,其内部持有一个类B的实例引用b,其实现方法的形式为复杂扩展语句 + 可能调用也可能不调用b的相应方法,即实现“在复用类B代码基础上,对类B进行深度扩展”需求。

7.2、实现

有两种实现形式:

  • 静态代理
  • 动态代理

7.2.1、静态代理

静态代理是指代理关系在编译期确定的代理模式。

示例代码如下[1]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 图像接口
interface Image {
void display();
}

// 真实图像类
class RealImage implements Image {
private String filename;

public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}

private void loadImageFromDisk() {
System.out.println("Loading image from disk: " + filename);
}

public void display() {
System.out.println("Displaying image: " + filename);
}
}

// 代理图像类
class ProxyImage implements Image {
private RealImage realImage;
private String filename;

public ProxyImage(String filename) {
this.filename = filename;
}

public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}

// 在这个示例中,Image接口定义了display方法,RealImage是实际的图像加载类,而ProxyImage是代理图像类
// 当ProxyImage的display方法被调用时,它会在需要时创建一个RealImage实例,并调用其display方法
public class ProxyPatternExample {
public static void main(String[] args) {
Image image = new ProxyImage("sample.jpg");

// 图像未加载,直到调用display()方法
image.display();

// 图像已加载,无需再次创建
image.display();
}
}

7.2.2、动态代理

动态代理是指代理类在编译前不存在,代理关系在运行时确定的代理模式。动态代理的实现方案有很多,比如“JDK动态代理”,“CGLIB动态代理”,“Javassist动态代理”和“ASM动态代理”等。

以下是一个使用JDK动态代理实现方案的示例代码:

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

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

// 接口
interface UserDao {

String insert(String data);

}

// 被代理类
class UserDaoImpl implements UserDao {

@Override
public String insert(String data) {
System.out.println("insert success");
return data;
}
}

public class JDKProxyExample {

public static void main(String[] args) {

UserDao proxyDao = new UserDaoImpl();

Class[] interfaces = {UserDao.class};

// 使用newProxyInstance静态方法生成一个代理对象
proxyDao = (UserDao) Proxy.newProxyInstance(JDKProxyExample.class.getClassLoader(), interfaces, new MyInvocationHandler(proxyDao));

// 代理对象的update方法实际实现逻辑见MyInvocationHandler
proxyDao.insert("10");

}
}

class MyInvocationHandler implements InvocationHandler {

Object obj;

public MyInvocationHandler(Object obj) {
this.obj = obj;
}


@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 被代理方法之前
System.out.println("before insert");

// 被代理类原方法的执行
Object res = method.invoke(obj, args);
System.out.println("insert result is " + res);

// 被代理方法之后
System.out.println("after insert");

return res;
}
}

7.3、JDK中的例子

  1. JDK 1.8中找不到静态代理的例子
  2. JDK 1.8中能找到很多使用动态代理的例子,比如“javax.management.JMXcreateProxy方法中的Object proxy = Proxy.newProxyInstance(interfaceClass.getClassLoader(), interfaces, handler)语句”

参考文献

[1]https://mp.weixin.qq.com/s/H16g_yZwRMIKagPb8oNIYQ
[2]https://juejin.cn/post/7031543219066404878
[3]https://juejin.cn/post/7029586308854972446

您的支持将鼓励我继续分享!