从 Spring-Framework 看 design pattern
Spring 的源代码可以说是一个很有学习价值的代码库,通过学习了一段时间的IoC和MVC代码,不仅对它们的流程有了进一步的了解,还学习多种设计模式,这次对Spring中涉及的设计模式进行总结。
Spring中涉及的设计模式有:
- 工厂方法 (Factory Method)
- 单例模式(Singleton)
- 原型模式 (Prototype)
- 适配器(Adapter)
- 包装器(Decorator)
- 代理(Proxy)
- 观察者(Observer)
- 策略(Strategy)
- 模板方法(Template Method)
下面会对以上的设计模式进行总结
工厂方法 (Factory Method)
Spring通过依赖注入将创建好的对象注入到调用它的对象之中,在这个地方,我们将对象创建的权利交给了IoC容器,不需要new就创建出了对象,降低了耦合,这里便采用了工厂方法,
优点:用户只需要关心所需产品对应的工厂,无需关心创建细节;加入新产品符合开闭原则,提高可扩展性。
缺点:类的个数容易过多,增加复杂度。
工厂方法举例
1
2
3
4
5
6
7
8<!-- 先配置工厂Bean,class指定该工厂的实现类,该Bean负责产生其他Bean实例 -->
<bean id="userFactory" class="factoryMethod.UserFactory"/>
<!-- 再引用工厂Bean来配置 其他Bean -->
<bean id="user" factory-bean="userFactory" factory-method="createPerson">
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg name="name" value="Creams"></constructor-arg>
</bean>
1 | //User.java |
单例模式(Singleton)
spring的bean的作用域默认为singleton,当作用域为singleton的时候,便涉及单例模式。单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1 | //饿汉式实现 |
原型模式 (Prototype)
Spring 中还有一个作用域是prototype,可以通过中scope=”prototype”来修改bean的作用域。其中就设计原型模式
原型模式就是从一个对象再创建另外一个可定制的对象, 而且不需要知道任何创建的细节。在Java中一般是通过克隆技术,以某个对象为原型复制新的对象。所以通过原型模式创建出的多个对象应该是不同的,这里与单例模式是相对的。
如何实现一个原型模式
1 | //Product.java |
适配器(Adapter)
在 Spring MVC中,当DispatcherServlet获取到Handler Mapping返回的Handler时,它不会直接执行handler,而是把它传给了Handler Adapter适配器进行适配执行。
同样在Spring AOP中对 BeforeAdvice、 AfterAdvice、 ThrowsAdvice 三种通知类型的支持实际上是借助适配器模式来实现的,它们都实现了Advice接口,这样的好处是使得框架允许用户向框架中加入自己想要支持的任何一种通知类型并且框架能够适配。
优点:能提高类的透明性和复用;目标类和适配器类解耦,提高程序扩展性;符合开闭原则。
缺点:增加了系统的复杂性;增加系统代码可读的难度。
如何实现适配器模式
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//Banner.java
public class Banner {
private String string;
public Banner (String string){
this.string = string;
}
public void showWithParen(){
System.out.println("("+string+")");
}
public void showWithAster(){
System.out.println("*"+string+"*");
}
}
//Print.java
public interface Print {
void pringWeak();
void printStrong();
}
//PrintBanner.java
public class PrintBanner extends Banner implements Print {
public PrintBanner(String string) {
super(string);
}
public void pringWeak() {
showWithParen();
}
public void printStrong() {
showWithAster();
}
}
//Main.java
public class Main {
public static void main(String[] args) {
Print print = new PrintBanner("Hello");
print.pringWeak();
print.printStrong();
}
}
/**
* 输出:
* (Hello)
* *Hello*
**/
包装器(Decorator)
查看org.springframework.cache.transaction
下的TransactionAwareCacheDecorator
该类实现了Cache接口,同时将Cache组合到类中成为了成员属性,所以可以大胆猜测TransactionAwareCacheDecorator是一个装饰类,不过这里并没有抽象装饰类,且TransactionAwareCacheDecorator没有子类,这里的装饰类关系并没有Java I/O中的装饰关系那么复杂。
实际上,Spring cache是对缓存使用的抽象,通过它我们可以在不侵入业务代码的基础上让现有代码即刻支持缓存。通过Spring的TransactionSynchronizationManager将其缓存操作与Spring管理的事务同步,仅在成功事务的提交之后执行实际的缓存操作。
优点:继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能;通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果;符合开闭原则。
缺点:会出现更多的代码,更多的类,增加程序复杂性;动态装饰时、多层装饰时会更复杂。
实现一个包装器模式
1 | //ABattercake.java |
代理(Proxy)
在Spring AOP中,通过创建Bean的代理对象,将切面织入Bean的切入点中,这个地方便用了代理模式。另外在MyBaits的动态代理创建Dao对象实例也用到了代理模式
优点:能将代理对象与真实被调用的目标对象分离;保护目标对象;增强目标对象。
缺点:会造成系统设计中类的数目增加;在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢;增加系统的复杂度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14// AbstractAutowireCapableBeanFactory.java
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
// 实例化的前置处理
// 给 BeanPostProcessors 一个机会用来返回一个代理类而不是真正的类实例
// AOP 的功能就是基于这个地方
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
} catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
如何实现一个代理模式
1 | //Printer.java |
观察者(Observer)
观察者模式又称为发布/订阅(Publish/Subscribe)模式,,因此我们可以用报纸期刊的订阅来形象的说明:报社方负责出版报纸.你订阅了该报社的报纸,那么只要报社发布了新报纸,就会通知你,或发到你手上.如果你不想再读报纸,可以取消订阅,这样,报社发布了新报纸就不会再通知你.
在Spring的applicationcontext中实现了事件机制,通过实现监听器ApplicationListener监听事件ApplicationEvent,当监听器监视到了事件的发布,就能做出一系列操作,这里就用到了观察者模式。
示例有GraphObserver和DightObserver。通过Random获取随机数后,调用notifyObservers方法(发布事件),发布事件以后会通过迭代器调用所有的Observer执行update。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
public interface Observer {
void update(NumberGenerator numberGenerator);
}
public abstract class NumberGenerator {
private ArrayList observers = new ArrayList();
public void addObserver(Observer observer){
observers.add(observer);
}
public void deleteObserver(Observer observer){
observers.remove(observer);
}
public void notifyObservers(){
Iterator iterator = observers.iterator();
while (iterator.hasNext()){
Observer observer = (Observer) iterator.next();
observer.update(this);
}
}
public abstract int getNumber();
public abstract void excute();
}
public class RandomNumberGenerator extends NumberGenerator {
private Random random = new Random();
private int number;
public int getNumber() {
return number;
}
public void excute() {
for (int i = 0; i < 20; i++) {
number = random.nextInt(50);
notifyObservers();
}
}
}
public class DightObserver implements Observer {
public void update(NumberGenerator numberGenerator) {
System.out.println("DightObserver:" + numberGenerator.getNumber());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class GraphObserver implements Observer {
public void update(NumberGenerator numberGenerator) {
System.out.print("GraphObserver");
int count = numberGenerator.getNumber();
for (int i = 0; i < count; i++) {
System.out.print("*");
}
System.out.println("");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) {
NumberGenerator generator = new RandomNumberGenerator();
Observer observer1 = new DightObserver();
Observer observer2 = new GraphObserver();
generator.addObserver(observer1);
generator.addObserver(observer2);
generator.excute();
}
}
/**
* DightObserver:31
* GraphObserver*******************************
* DightObserver:42
* GraphObserver******************************************
* DightObserver:39
* GraphObserver***************************************
* DightObserver:6
* GraphObserver******
* DightObserver:22
* GraphObserver**********************
* DightObserver:4
* GraphObserver****
* DightObserver:7
* GraphObserver*******
* DightObserver:49
* GraphObserver*************************************************
* DightObserver:47
* GraphObserver***********************************************
* DightObserver:26
* GraphObserver**************************
* DightObserver:23
* GraphObserver***********************
* DightObserver:39
* GraphObserver***************************************
* DightObserver:37
* GraphObserver*************************************
* DightObserver:21
* GraphObserver*********************
* DightObserver:29
* GraphObserver*****************************
* DightObserver:32
* GraphObserver********************************
* DightObserver:38
* GraphObserver**************************************
* DightObserver:25
* GraphObserver*************************
* DightObserver:9
* GraphObserver*********
* DightObserver:6
* GraphObserver******
*
*/
策略(Strategy)
Spring 中在实例化对象的时候用到策略模式, 在 SimpleInstantiationStrategy 有使用。
采用实现部分、抽象部分的策略。为了更好的扩展性,把一部分再次抽象,后面可以采用多种实现方式。
Spring 中角色分工很明确,创建对象的时候先通过 ConstructorResolver 找到对应的实例化方法和参数,再通过实例化策略 InstantiationStrategy 进行实例化,根据创建对象的三个分支(工厂方法、有参构造方法、无参构造方法), InstantiationStrategy 提供了
如果工厂方法实例化直接用反射创建对象,如果是构造方法实例化的则判断是否有 MethodOverrides,如果无 MethodOverrides 也是直接用反射,如果有 MethodOverrides 就需要用 Cglib 实例化对象,SimpleInstantiationStrategy 把通过 Cglib 实例化的任务交给了它的子类 CglibSubclassingInstantiationStrategy。
- 优点:符合开闭原则;避免使用多重条件转移语句;提高算法的保密性和安全性。
- 缺点:客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
如何实现策略模式
1 | //PromotionStrategy.java |
模板方法(Template Method)
在Spring中常常见到父类在某个方法的实现上没有具体代码,空实现或者抛出异常,将方法留给子类,这种情况在Spring中十分常见,这里就用到了模板方法
优点:提高复用性;提高扩展性;符合开闭原则。
缺点:类数目增加,增加了系统实现的复杂度;继承关系自身缺点,即如果父类添加新的抽象方法,所有子类都要改一遍。
如何实现模板方法
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
98public abstract class AbstractDisplay {
public abstract void open();
public abstract void print();
public abstract void close();
public final void display(){
open();
for (int i = 0; i < 5; i++) {
print();
}
close();
}
}
public class CharDisplay extends AbstractDisplay {
private char ch;
public CharDisplay(char ch) {
this.ch = ch;
}
public void open() {
System.out.print("<<");
}
public void print() {
System.out.print(ch);
}
public void close() {
System.out.println(">>");
}
}
public class StringDisplay extends AbstractDisplay {
private String string;
private int width;
public StringDisplay(String string) {
this.string = string;
this.width = string.getBytes().length;
}
public void open() {
printLine();
}
public void print() {
System.out.println("|" + string + "|");
}
public void close() {
printLine();
}
private void printLine(){
System.out.print("+");
for (int i = 0; i < width; i++) {
System.out.print("-");
}
System.out.println("+");
}
}
public class Main {
public static void main(String[] args) {
AbstractDisplay d1 = new CharDisplay('C');
AbstractDisplay d2 = new StringDisplay("Hello World");
d1.display();
d2.display();
}
}
/**
* <<CCCCC>>
* +-----------+
* |Hello World|
* |Hello World|
* |Hello World|
* |Hello World|
* |Hello World|
* +-----------+
*/