结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

结构型模式分为以下 7 种:

  • 代理模式
  • 适配器模式
  • 装饰者模式
  • 桥接模式
  • 外观模式
  • 组合模式
  • 享元模式

代理模式

概念

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

结构

代理(Proxy)模式分为三种角色:

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

静态代理

如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子就是典型的代理模式,火车站是目标对象,代售点是代理对象。

image-20230517233603479

抽象主题

1
2
3
4
5
6
7
8
9
/**
* 卖火车票接口
* @author Ether
*/
public interface SellTickets {

void sell();

}

真实主题

1
2
3
4
5
6
7
8
9
10
/**
* 火车站
* @author Ether
*/
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("火车站卖票");
}
}

代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 代售点
* @author Ether
*/
public class ProxyPoint implements SellTickets{

//声明火车站对象
private TrainStation station = new TrainStation();

@Override
public void sell() {
System.out.println("进行收费");
station.sell();
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
/**
* @author Ether
*/
public class App {

public static void main(String[] args) {
//静态代理
ProxyPoint point = new ProxyPoint();
point.sell();
}
}

动态代理

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* 获取代理对象的工厂类
* @author Ether
*/
public class ProxyFactory {

//声明目标对象
private TrainStation station = new TrainStation();

public SellTickets getProxyObject(){
/**
* ClassLoader loader: 类加载器,用于加载代理类
* Class<?>[] interfaces: 代理类实现接口的字节码对象
* InvocationHandler: 代理对象调用处理
*/
SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {

/**
*
* @param proxy 代理对象
* @param method 对接口中方法进行封装的代理对象
* @param args 调用方法的实际参数
* @return 方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println("Invoke方法执行");
System.out.println("代售点收取服务费(JDK动态代理)");
//调用目标方法
Object invoke = method.invoke(station, args);
return invoke;
}
}
);

return proxyObject;
}

}

代理类

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
package com.sun.proxy;

import com.xxx.SellTickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements SellTickets {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.xxx.SellTickets").getMethod("sell", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}

public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final void sell() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @author Ether
*/
public class App {

public static void main(String[] args) {
ProxyPoint point = new ProxyPoint();
point.sell();

//获取代理对象
//创建代理工厂
ProxyFactory factory = new ProxyFactory();
//获取代理对象
SellTickets proxyObject = factory.getProxyObject();
//调用方法
proxyObject.sell();

// System.out.println(proxyObject.getClass());
// while (true){}
}
}

执行流程如下:

1. 在测试类中通过代理对象调用sell()方法
2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法

CGLIB动态代理

代理工厂

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
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* 代理对象工厂
* 获取代理对象(cglib)
* @author Ether
*/
public class CglibProxyFactory implements MethodInterceptor {

//声明火车站对象
TrainStation station = new TrainStation();

public TrainStation getProxyObject(){
//创建Enhancer对象,类似JDK中的proxy类
Enhancer enhancer = new Enhancer();

//设置父类字节码对象
enhancer.setSuperclass(TrainStation.class);

//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TrainStation proxyObject = (TrainStation) enhancer.create();
return proxyObject;
}

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代售点收取服务费(Cglib动态代理)");

Object invoke = method.invoke(station, objects);

return invoke;
}
}

代理类

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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
package com.xxx;

import com.xxx.TrainStation;
import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class TrainStation$$EnhancerByCGLIB$$f3f5c491
extends TrainStation
implements Factory {
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$sell$0$Method;
private static final MethodProxy CGLIB$sell$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;

public TrainStation$$EnhancerByCGLIB$$f3f5c491() {
TrainStation$$EnhancerByCGLIB$$f3f5c491 trainStation$$EnhancerByCGLIB$$f3f5c491 = this;
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BIND_CALLBACKS(trainStation$$EnhancerByCGLIB$$f3f5c491);
}

static {
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$STATICHOOK1();
}

protected final void finalize() throws Throwable {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$finalize$1$Method, CGLIB$emptyArgs, CGLIB$finalize$1$Proxy);
return;
}
super.finalize();
}

public final boolean equals(Object object) {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object2 = methodInterceptor.intercept(this, CGLIB$equals$2$Method, new Object[]{object}, CGLIB$equals$2$Proxy);
return object2 == null ? false : (Boolean)object2;
}
return super.equals(object);
}

public final String toString() {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
return (String)methodInterceptor.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy);
}
return super.toString();
}

public final int hashCode() {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);
return object == null ? 0 : ((Number)object).intValue();
}
return super.hashCode();
}

protected final Object clone() throws CloneNotSupportedException {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
return methodInterceptor.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy);
}
return super.clone();
}

public Object newInstance(Callback callback) {
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$SET_THREAD_CALLBACKS(new Callback[]{callback});
TrainStation$$EnhancerByCGLIB$$f3f5c491 trainStation$$EnhancerByCGLIB$$f3f5c491 = new TrainStation$$EnhancerByCGLIB$$f3f5c491();
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$SET_THREAD_CALLBACKS(null);
return trainStation$$EnhancerByCGLIB$$f3f5c491;
}

public Object newInstance(Callback[] callbackArray) {
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$SET_THREAD_CALLBACKS(callbackArray);
TrainStation$$EnhancerByCGLIB$$f3f5c491 trainStation$$EnhancerByCGLIB$$f3f5c491 = new TrainStation$$EnhancerByCGLIB$$f3f5c491();
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$SET_THREAD_CALLBACKS(null);
return trainStation$$EnhancerByCGLIB$$f3f5c491;
}

public Object newInstance(Class[] classArray, Object[] objectArray, Callback[] callbackArray) {
TrainStation$$EnhancerByCGLIB$$f3f5c491 trainStation$$EnhancerByCGLIB$$f3f5c491;
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$SET_THREAD_CALLBACKS(callbackArray);
Class[] classArray2 = classArray;
switch (classArray.length) {
case 0: {
trainStation$$EnhancerByCGLIB$$f3f5c491 = new TrainStation$$EnhancerByCGLIB$$f3f5c491();
break;
}
default: {
throw new IllegalArgumentException("Constructor not found");
}
}
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$SET_THREAD_CALLBACKS(null);
return trainStation$$EnhancerByCGLIB$$f3f5c491;
}

public void setCallback(int n, Callback callback) {
switch (n) {
case 0: {
this.CGLIB$CALLBACK_0 = (MethodInterceptor)callback;
break;
}
}
}

public final void sell() {
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$sell$0$Method, CGLIB$emptyArgs, CGLIB$sell$0$Proxy);
return;
}
super.sell();
}

public void setCallbacks(Callback[] callbackArray) {
Callback[] callbackArray2 = callbackArray;
TrainStation$$EnhancerByCGLIB$$f3f5c491 trainStation$$EnhancerByCGLIB$$f3f5c491 = this;
this.CGLIB$CALLBACK_0 = (MethodInterceptor)callbackArray[0];
}

public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] callbackArray) {
CGLIB$STATIC_CALLBACKS = callbackArray;
}

public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] callbackArray) {
CGLIB$THREAD_CALLBACKS.set(callbackArray);
}

public Callback[] getCallbacks() {
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BIND_CALLBACKS(this);
TrainStation$$EnhancerByCGLIB$$f3f5c491 trainStation$$EnhancerByCGLIB$$f3f5c491 = this;
return new Callback[]{this.CGLIB$CALLBACK_0};
}

public Callback getCallback(int n) {
MethodInterceptor methodInterceptor;
TrainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BIND_CALLBACKS(this);
switch (n) {
case 0: {
methodInterceptor = this.CGLIB$CALLBACK_0;
break;
}
default: {
methodInterceptor = null;
}
}
return methodInterceptor;
}

final void CGLIB$finalize$1() throws Throwable {
super.finalize();
}

final void CGLIB$sell$0() {
super.sell();
}

final boolean CGLIB$equals$2(Object object) {
return super.equals(object);
}

final int CGLIB$hashCode$4() {
return super.hashCode();
}

final String CGLIB$toString$3() {
return super.toString();
}

final Object CGLIB$clone$5() throws CloneNotSupportedException {
return super.clone();
}

public static MethodProxy CGLIB$findMethodProxy(Signature signature) {
String string = ((Object)signature).toString();
switch (string.hashCode()) {
case -1574182249: {
if (!string.equals("finalize()V")) break;
return CGLIB$finalize$1$Proxy;
}
case -508378822: {
if (!string.equals("clone()Ljava/lang/Object;")) break;
return CGLIB$clone$5$Proxy;
}
case 1826985398: {
if (!string.equals("equals(Ljava/lang/Object;)Z")) break;
return CGLIB$equals$2$Proxy;
}
case 1913648695: {
if (!string.equals("toString()Ljava/lang/String;")) break;
return CGLIB$toString$3$Proxy;
}
case 1978249955: {
if (!string.equals("sell()V")) break;
return CGLIB$sell$0$Proxy;
}
case 1984935277: {
if (!string.equals("hashCode()I")) break;
return CGLIB$hashCode$4$Proxy;
}
}
return null;
}

static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class<?> clazz = Class.forName("com.xxx.TrainStation$$EnhancerByCGLIB$$f3f5c491");
Class<?> clazz2 = Class.forName("com.xxx.TrainStation");
CGLIB$sell$0$Method = ReflectUtils.findMethods(new String[]{"sell", "()V"}, clazz2.getDeclaredMethods())[0];
CGLIB$sell$0$Proxy = MethodProxy.create(clazz2, clazz, "()V", "sell", "CGLIB$sell$0");
clazz2 = Class.forName("java.lang.Object");
Method[] methodArray = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, clazz2.getDeclaredMethods());
CGLIB$finalize$1$Method = methodArray[0];
CGLIB$finalize$1$Proxy = MethodProxy.create(clazz2, clazz, "()V", "finalize", "CGLIB$finalize$1");
CGLIB$equals$2$Method = methodArray[1];
CGLIB$equals$2$Proxy = MethodProxy.create(clazz2, clazz, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
CGLIB$toString$3$Method = methodArray[2];
CGLIB$toString$3$Proxy = MethodProxy.create(clazz2, clazz, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
CGLIB$hashCode$4$Method = methodArray[3];
CGLIB$hashCode$4$Proxy = MethodProxy.create(clazz2, clazz, "()I", "hashCode", "CGLIB$hashCode$4");
CGLIB$clone$5$Method = methodArray[4];
CGLIB$clone$5$Proxy = MethodProxy.create(clazz2, clazz, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
}

private static final void CGLIB$BIND_CALLBACKS(Object object) {
block2: {
Object object2;
block3: {
TrainStation$$EnhancerByCGLIB$$f3f5c491 trainStation$$EnhancerByCGLIB$$f3f5c491 = (TrainStation$$EnhancerByCGLIB$$f3f5c491)object;
if (trainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BOUND) break block2;
trainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$BOUND = true;
object2 = CGLIB$THREAD_CALLBACKS.get();
if (object2 != null) break block3;
object2 = CGLIB$STATIC_CALLBACKS;
if (CGLIB$STATIC_CALLBACKS == null) break block2;
}
trainStation$$EnhancerByCGLIB$$f3f5c491.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])object2)[0];
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author Ether
*/
public class App {

public static void main(String[] args) {
//Cglib
CglibProxyFactory factory1 = new CglibProxyFactory();
TrainStation proxyObject1 = factory1.getProxyObject();
proxyObject1.sell();
}
}

三种代理的对比

  • jdk代理和CGLIB代理

    使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

  • 动态代理和静态代理

    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

优缺点

优点

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

缺点

增加了系统的复杂度;

使用场景

  • 远程(Remote)代理

    本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

  • 防火墙(Firewall)代理

    当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

  • 保护(Protect or Access)代理

    控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

适配器模式

概念

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

结构

适配器模式(Adapter)包含以下主要角色:

  • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
  • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

类适配器模式

实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。

现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。

image-20230517234703943

目标接口

1
2
3
4
5
6
7
8
9
10
/**
* 目标接口
* @author Ether
*/
public interface SDCard {

public String readSD();

public void writeSD(String msg);
}

目标接口实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 具体SD卡
* @author Ether
*/
public class SDCardImpl implements SDCard{
@Override
public String readSD() {
String msg = "read SDCard";
return msg;
}

@Override
public void writeSD(String msg) {
System.out.println("SDCard write msg:"+msg);
}
}

适配者

1
2
3
4
5
6
7
8
9
10
11
/**
* 适配者接口
* @author Ether
*/
public interface TFCard {

//从TF卡中读取数据
String readTF();
//往TF卡写数据
void writeTF(String msg);
}

适配者实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 适配者类
* @author Ether
*/
public class TFCardImpl implements TFCard{

@Override
public String readTF() {
String msg = "read TFCard";
return msg;
}

@Override
public void writeTF(String msg) {
System.out.println("TFCard write msg:"+msg);
}
}

主调用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 计算机类
* @author Ether
*/
public class Computer {

public String readSD(SDCard sd){
if (sd == null){
throw new NullPointerException("SDCard is not null");
}
return sd.readSD();
}

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author Ether
*/
public class App {
public static void main(String[] args) {
Computer computer = new Computer();
System.out.println(computer.readSD(new SDCardImpl()));

//适配器读取TF
Computer computer1 = new Computer();
SDAdapterTF sdAdapterTF = new SDAdapterTF();
System.out.println(computer1.readSD(sdAdapterTF));
}
}

类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。

对象适配器模式

实现方式:对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。

image-20230518152324682

适配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 对象适配器
* @author Ether
*/
public class SDObjectAdapterTF implements SDCard{

private TFCard tfCard;

public SDObjectAdapterTF(TFCard tfCard){
this.tfCard = tfCard;
}

@Override
public String readSD() {
System.out.println("adapter read tf card");
return tfCard.readTF();
}

@Override
public void writeSD(String msg) {
System.out.println("adapter write tf card");
tfCard.writeTF(msg);
}
}

注意:还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可。

使用场景

  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。

装饰者模式

概念

指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。

结构

装饰(Decorator)模式中的角色:

  • 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

实现

快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。

202305181557471

抽象构件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* 快餐类
* @author Ether
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public abstract class FastFood {

private float price;

private String desc;

public abstract float cost();
}

具体构件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 炒饭,具体构件角色
* @author Ether
*/
public class FriedRice extends FastFood{

public FriedRice(){
super(10,"炒饭");
}

@Override
public float cost() {
return getPrice();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 炒面类
* @author Ether
*/
public class FriedNoodles extends FastFood{

public FriedNoodles(){
super(12,"炒面");
}

@Override
public float cost() {
return getPrice();
}
}

抽象装饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 装饰者类
* @author Ether
*/
public abstract class Garnish extends FastFood{

private FastFood fastFood;

public FastFood getFastFood() {
return fastFood;
}

public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}

public Garnish(FastFood fastFood, float price, String desc) {
super(price,desc);
this.fastFood = fastFood;
}

}

具体装饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 鸡蛋配料
* @author Ether
*/

public class Egg extends Garnish {

public Egg(FastFood fastFood) {
super(fastFood,1,"鸡蛋");
}

public float cost() {
return getPrice() + getFastFood().getPrice();
}

@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 培根配料
* @author Ether
*/
public class Bacon extends Garnish {

public Bacon(FastFood fastFood) {

super(fastFood,2,"培根");
}

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

@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}

测试

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
/**
* @author Ether
*/
public class App {

public static void main(String[] args) {
//点一份炒饭
FastFood food = new FriedRice();
//花费的价格
System.out.println(food.getDesc() + " " + food.cost() + "元");

System.out.println("========");
//点一份加鸡蛋的炒饭
FastFood food1 = new FriedRice();

food1 = new Egg(food1);
//花费的价格
System.out.println(food1.getDesc() + " " + food1.cost() + "元");

System.out.println("========");
//点一份加培根的炒面
FastFood food2 = new FriedNoodles();
food2 = new Bacon(food2);
//花费的价格
System.out.println(food2.getDesc() + " " + food2.cost() + "元");
}

}

优缺点

优点

  • 装饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。

  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点

  • 装饰者的对象和它装饰的对象本质上是完全不同的,装饰模式会生成许多的对象,导致区分各种对象变得困难

  • 由于使用相同的标识,对于程序的理解和拍错过程的难度也会随之增加

使用场景

  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。

    不能采用继承的情况主要有两类:

    • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
    • 第二类是因为类定义不能继承(如final类)
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

代理和装饰者的区别

静态代理和装饰者模式的区别:

  • 相同点:
    • 都要实现与目标类相同的业务接口
    • 在两个类中都要声明目标对象
    • 都可以在不修改目标类的前提下增强目标方法
  • 不同点:
    • 目的不同
      装饰者是为了增强目标对象
      静态代理是为了保护和隐藏目标对象
    • 获取目标对象构建的地方不同
      装饰者是由外界传递进来,可以通过构造方法传递
      静态代理是在代理类内部创建,以此来隐藏目标对象

桥接模式

概念

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

结构

桥接(Bridge)模式包含以下主要角色:

  • 抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。

实现

开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式。

image-20230518195358121

抽象化角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 操作系统类,抽象角色
* @author Ether
*/
public abstract class OperatingSystem {

protected VideoFile videoFile;

public OperatingSystem(VideoFile videoFile){
this.videoFile = videoFile;
}

public abstract void play(String fileName);

}

扩展抽象化角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 扩展抽象化角色,Windows操作系统
* @author Ether
*/
public class Windows extends OperatingSystem{

public Windows(VideoFile videoFile) {
super(videoFile);
}

@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Mac操作系统,扩展抽象化角色
* @author Ether
*/
public class Mac extends OperatingSystem{

public Mac(VideoFile videoFile) {
super(videoFile);
}

@Override
public void play(String fileName) {
videoFile.decode(fileName);
}
}

实现化角色

1
2
3
4
5
6
7
8
9
10
/**
* 视频文件(实现化角色)
* @author Ether
*/
public interface VideoFile {

//解码功能
void decode(String fileName);

}

具体实现化角色

1
2
3
4
5
6
7
8
9
10
/**
* 具体实现化角色,rmvb视频文件
* @author Ether
*/
public class RmvbFile implements VideoFile{
@Override
public void decode(String fileName) {
System.out.println("rmvb视频文件:" + fileName);
}
}
1
2
3
4
5
6
7
8
9
10
/**
* AVI视频角色,实现化角色
* @author Ether
*/
public class AviFile implements VideoFile{
@Override
public void decode(String fileName) {
System.out.println("avi视频文件:" + fileName);
}
}

测试

1
2
3
4
5
6
7
8
9
10
/**
* @author Ether
*/
public class App {
public static void main(String[] args) {
//创建Mac操作系统
OperatingSystem system = new Mac(new AviFile());
system.play("黑豹2");
}
}

特点

  • 特点:如果现在还有一种视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化。

    如:如果现在还有一种视频文件类型wmv,我们只需要再定义一个类实现VideoFile接口即可,其他类不需要发生变化。

  • 实现细节对客户透明

使用场景

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
  • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。

外观模式

概念

又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

结构

外观(Facade)模式包含以下主要角色:

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。

实现

智能家电控制:晚上每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以可以实现一个智能家电控制系统,通过语音直接控制这些智能家电的开启和关闭。类图如下:

image-20230521155413957

外观角色

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
/**
* 外观类,用户主要和该类对象进行交互
* @author Ether
*/
public class SmartApplicationFacade {

private Light light;
private TV tv;
private AirCondition airCondition;

public SmartApplicationFacade() {
light = new Light();
tv = new TV();
airCondition = new AirCondition();
}

public void say(String message){
if (message.contains("打开")){
on();
}else if (message.contains("关闭")){
off();
}else {
System.out.println("不太清楚要做什么");
}
}

public void on(){
light.on();
tv.on();
airCondition.on();
}

public void off(){
light.off();
tv.off();
airCondition.off();
}

}

子系统角色

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 电灯
* @author Ether
*/
public class Light {

public void on(){
System.out.println("打开电灯");
}
public void off(){
System.out.println("关闭电灯");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 电视类
* @author Ether
*/
public class TV {
public void on(){
System.out.println("打开电视");
}
public void off(){
System.out.println("关闭电视");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 空调
* @author Ether
*/
public class AirCondition {
public void on(){
System.out.println("打开空调");
}
public void off(){
System.out.println("关闭空调");
}
}

测试

1
2
3
4
5
6
7
8
9
10
/**
* @author Ether
*/
public class App {
public static void main(String[] args) {
SmartApplicationFacade facade = new SmartApplicationFacade();
facade.say("打开家电");
facade.say("关闭家电");
}
}

优缺点

优点

  • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。

缺点

  • 不符合开闭原则,修改很麻烦

使用场景

  • 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

组合模式

概念

又名部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

结构

组合模式主要包含三种角色:

  • 抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
  • 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
  • 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。

分类

在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式。

  • 透明组合模式

    透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent 声明了 addremovegetChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。

    透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)

  • 安全组合模式

    在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。

实现

打印软件菜单,打印出其包含的所有菜单以及菜单项的名称。

image-20230523165509783

抽象根节点

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
/**
* 菜单组件,抽象根节点
* @author Ether
*/
public abstract class MenuComponent {

//菜单组件名称
protected String name;

//菜单组件层级
protected int level;

//添加子菜单
public void add(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}

//移除子菜单
public void remove(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}

//获取指定子菜单
public MenuComponent getChild(int index){
throw new UnsupportedOperationException();
}

public String getName() {
return name;
}
//打印菜单名称,包括子菜单和菜单项
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
import java.util.ArrayList;
import java.util.List;

/**
* 菜单类,树枝节点
* @author Ether
*/
public class Menu extends MenuComponent{

//可以有多个子菜单
private List<MenuComponent> menuComponentList = new ArrayList<>();

//构造方法
public Menu(String name,int level){
this.name = name;
this.level = level;
}

@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}

@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}

@Override
public MenuComponent getChild(int index) {
return menuComponentList.get(index);
}

@Override
public void print() {
StringBuilder word = new StringBuilder();
for (int i = 0; i < level-1; i++) {
word.append("-");
}
//打印菜单名称,打印子菜单项
System.out.println(word+name);

for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}

叶子节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 菜单项类,叶子节点
* @author Ether
*/
public class MenuItem extends MenuComponent{

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

@Override
public void print() {
StringBuilder word = new StringBuilder();
for (int i = 0; i < level-1; i++) {
word.append("-");
}
System.out.println(word+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
/**
* @author Ether
*/
public class App {
public static void main(String[] args) {
MenuComponent menu1 = new Menu("菜单管理",2);

menu1.add(new MenuItem("页面访问",3));
menu1.add(new MenuItem("展开菜单",3));
menu1.add(new MenuItem("删除菜单",3));

MenuComponent menu2 = new Menu("权限管理",2);

menu2.add(new MenuItem("页面访问",3));
menu2.add(new MenuItem("提交保存",3));

MenuComponent menu3 = new Menu("角色管理",2);

menu3.add(new MenuItem("删除菜单",3));
menu3.add(new MenuItem("新增角色",3));

Menu menu = new Menu("系统管理",1);
menu.add(menu1);
menu.add(menu2);
menu.add(menu3);

menu.print();
}
}

特点

  • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
  • 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
  • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

使用场景

组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多级目录呈现等树形结构数据的操作。

享元模式

努力写作中

img.png