从 Spring-Framework 看 design pattern

从 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
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
//User.java
public class User {
int id;
String name;

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

public int getId() {

return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

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

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}

//UserFactory.java
public class UserFactory {
public User createPerson(int id, String name){
return new User(id, name);
}
}

//test
public class FactortMethod {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.toString());
}
}

//输出
//User{id=1, name='Creams'}

单例模式(Singleton)

spring的bean的作用域默认为singleton,当作用域为singleton的时候,便涉及单例模式。单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。

  • 优点:在内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用;设置全局访问点,严格控制访问。

  • 缺点:可扩展性较差。

    如何实现单例模式

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
//饿汉式实现
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){
System.out.println("生成一个实例.");
}
public static Singleton getInstance(){
return singleton;
}
}

//懒汉式实现(同步)
public synchronized class Singleton {
private static Singleton singleton = null;
private Singleton(){
System.out.println("生成一个实例.");
}
public static Singleton getInstance(){
if (singleton == null)
singleton = new Singleton();
return singleton;
}
}

//懒汉式实现(双重校验锁)
public class Singleton {
private static Singleton singleton = null;
private Singleton(){
System.out.println("生成一个实例.");
}
public static Singleton getInstance(){
if (singleton == null){
synchronized(Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}

原型模式 (Prototype)

Spring 中还有一个作用域是prototype,可以通过中scope=”prototype”来修改bean的作用域。其中就设计原型模式

原型模式就是从一个对象再创建另外一个可定制的对象, 而且不需要知道任何创建的细节。在Java中一般是通过克隆技术,以某个对象为原型复制新的对象。所以通过原型模式创建出的多个对象应该是不同的,这里与单例模式是相对的。

如何实现一个原型模式

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
//Product.java
public interface Product extends Cloneable {
public abstract void use (String s);
public abstract Product createClone();
}

//Manager.java
public class Manager {
private HashMap showcase = new HashMap();
public void register(String name, Product proto){
showcase.put(name, proto);
}
public Product create (String protoName){
Product p = (Product)showcase.get(protoName);
return p.createClone();
}
}

//User.java
public class User implements Product {

public void use(String s) {
System.out.println("use User " + s);
}

public Product createClone() {
Product product = null;
try {
product = (Product)clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return product;
}
}

//Main.java
public class Main {
public static void main(String[] args) {
Manager manager = new Manager();
User user = new User();
manager.register("user", user);


User user1 = (User) manager.create("user");
user1.use("Creams");

System.out.println("user and user1 are the same object:" + user.equals(user1));
}
}



/**
* 输出:
*
* use User Creams
* user and user1 are the same object:false
*
**/

适配器(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
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
//ABattercake.java
public abstract class ABattercake {
protected abstract String getDesc();
protected abstract int cost();
}

//Battercake.java
public class Battercake extends ABattercake {
@Override
protected String getDesc() {
return "煎饼";
}

@Override
protected int cost() {
return 8;
}
}

//AbstractDecorator.java
public class AbstractDecorator extends ABattercake {
private ABattercake aBattercake;

public AbstractDecorator(ABattercake aBattercake) {
this.aBattercake = aBattercake;
}

@Override
protected String getDesc() {
return this.aBattercake.getDesc();
}

@Override
protected int cost() {
return this.aBattercake.cost();
}
}

//EggDecorator.java
public class EggDecorator extends AbstractDecorator {
public EggDecorator(ABattercake aBattercake) {
super(aBattercake);
}

@Override
protected String getDesc() {
return super.getDesc() + " 加一个鸡蛋";
}

@Override
protected int cost() {
return super.cost()+1;
}
}

//SausageDecorator.java
public class SausageDecorator extends AbstractDecorator {
public SausageDecorator(ABattercake aBattercake) {
super(aBattercake);
}

@Override
protected String getDesc() {
return super.getDesc() + " 加一根香肠";
}

@Override
protected int cost() {
return super.cost()+2;
}
}

//Test.java
public class Test {
public static void main(String[] args) {
ABattercake aBattercake;
aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
aBattercake = new EggDecorator(aBattercake);
aBattercake = new SausageDecorator(aBattercake);

System.out.println(aBattercake.getDesc() + " 销售价格:" + aBattercake.cost());
}
}


//煎饼 加一个鸡蛋 加一个鸡蛋 加一根香肠 销售价格:12

代理(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
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
//Printer.java
public class Printer implements Printable {
private String name;
public Printer(){
heavyJob("Print 实例对象生成中");
}

public Printer(String name){
this.name = name;
heavyJob("Print 的实例对象生成中 (" + name + ")");
}

public String getPrinterName() {
return name;
}

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

public void print(String string){
System.out.println("=== " + name + " ===");
System.out.println(string);
}

public void heavyJob(String msg){
System.out.print(msg);
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(".");
}
System.out.println("结束。");
}
}



//Printable.java
public interface Printable {
String getPrinterName();
void setPrinterName(String name);
void print(String string);
}

//PrinterProxy.java
public class PrinterProxy implements Printable{
private String name;

private Printer real;

public PrinterProxy(){

}

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

public synchronized void setPrinterName(String name) {
if (real != null){
real.setPrinterName(name);
}
this.name = name;
}

public String getPrinterName() {
return name;
}

public void print(String string) {
realize();
real.print(string);
}

public synchronized void realize(){
if (real == null)
real = new Printer(name);
}
}

//Main.java
public class Main {

public static void main(String[] args) {
Printable printable = new PrinterProxy("Creams");
System.out.println("现在名字是" + printable.getPrinterName() + "。");
printable.setPrinterName("Bob");
System.out.println("现在名字是" + printable.getPrinterName() + "。");
printable.print("Hello World");

}


}

/**
* 现在名字是Creams。
* 现在名字是Bob。
* Print 的实例对象生成中 (Bob).....结束。
* === Bob ===
* Hello World
*/

观察者(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
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
//PromotionStrategy.java
public interface PromotionStrategy {
void doPromotion();
}

//FanXianPromotionStrategy.java
public class FanXianPromotionStrategy implements PromotionStrategy {
public void doPromotion() {
System.out.println("返现促销,返回的金额存放到用户余额中");
}
}

//LiJianPromotionStrategy.java
public class LiJianPromotionStrategy implements PromotionStrategy{
public void doPromotion() {
System.out.println("立减促销,课程的价格直接减去配置的价格");
}
}

//ManJianPromotionStratehy.java
public class ManJianPromotionStratehy implements PromotionStrategy {
public void doPromotion() {
System.out.println("满减促销,满200-20元");
}
}

//PromotionActivity.java
public class PromotionActivity {
private PromotionStrategy promotionStrategy;

public PromotionActivity(PromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}

public void executePromotionStrategy(){
promotionStrategy.doPromotion();
}
}


//Test.java
public class Test {
public static void main(String[] args) {
PromotionActivity promotionActivity618 = new PromotionActivity(new LiJianPromotionStrategy());
PromotionActivity promotionActivity1111 = new PromotionActivity(new FanXianPromotionStrategy());
promotionActivity618.executePromotionStrategy();
promotionActivity1111.executePromotionStrategy();
}
}

模板方法(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
    98
    public 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|
    * +-----------+
    */

参考资料