创建型模式

本文主要介绍一下设计模式中的创建型模式。

开篇之前,先说一下,什么是设计模式,一言以蔽之,设计模式是代码设计经验的总结。设计模式的原理非常简单,但是也绝不可能通过一篇文章或者一本书来完全掌握(在学习任何一种知识的时候都应该博采众长,取舍有度,而不是盯着一本书或者一篇文章看,偏信一家之言,要如鲁迅所说:“运用脑髓,放出眼光,自己来拿!”)。其次,死记硬背设计模式的UML图或者示例代码没有意义,和OOP编程一下,设计模式本质上是一种思想,须得领会后方能灵活运用。

设计模式主要基于以下两个面向对象的编程思想:

  • 面向接口编程
  • 组合优于继承

设计模式还有所谓的六大原则,这里择其重点说明一下:

  • 对扩展开放,对修改关闭
  • 任何基类可以出现的地方,子类一定可以出现。理解这个原则:只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。
  • 使用多个隔离的接口,比使用单个接口要好
  • 一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立

创建型模式,顾名思义,就是一种创建对象的方法。

创建型模式有五种:工厂、抽象工厂、单例、建造者、原型。

工厂

工厂模式在工厂、策略和桥接这篇文章中已经介绍过,这里不再赘述。

抽象工厂

抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。

先看图:

再看代码:

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
public interface Shape {
void draw();
}

public class Rectangle implements Shape {

@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}

public class Square implements Shape {

@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}

public interface Color {
void fill();
}

public class Green implements Color {

@Override
public void fill() {
System.out.println("Inside Green::fill() method.");
}
}

public class Blue implements Color {

@Override
public void fill() {
System.out.println("Inside Blue::fill() method.");
}
}

public abstract class AbstractFactory {
public abstract Color getColor(String color);
public abstract Shape getShape(String shape) ;
}

public class ShapeFactory extends AbstractFactory {

@Override
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}

@Override
public Color getColor(String color) {
return null;
}
}

public class ColorFactory extends AbstractFactory {

@Override
public Shape getShape(String shapeType){
return null;
}

@Override
public Color getColor(String color) {
if(color == null){
return null;
}
if(color.equalsIgnoreCase("RED")){
return new Red();
} else if(color.equalsIgnoreCase("GREEN")){
return new Green();
} else if(color.equalsIgnoreCase("BLUE")){
return new Blue();
}
return null;
}
}

public class FactoryProducer {
public static AbstractFactory getFactory(String choice){
if(choice.equalsIgnoreCase("SHAPE")){
return new ShapeFactory();
} else if(choice.equalsIgnoreCase("COLOR")){
return new ColorFactory();
}
return null;
}
}

public class AbstractFactoryPatternDemo {
public static void main(String[] args) {

//获取形状工厂
AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");

//获取形状为 Circle 的对象
Shape shape1 = shapeFactory.getShape("CIRCLE");

//调用 Circle 的 draw 方法
shape1.draw();

//获取形状为 Rectangle 的对象
Shape shape2 = shapeFactory.getShape("RECTANGLE");

//调用 Rectangle 的 draw 方法
shape2.draw();

//获取形状为 Square 的对象
Shape shape3 = shapeFactory.getShape("SQUARE");

//调用 Square 的 draw 方法
shape3.draw();

//获取颜色工厂
AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");

//获取颜色为 Red 的对象
Color color1 = colorFactory.getColor("RED");

//调用 Red 的 fill 方法
color1.fill();

//获取颜色为 Green 的对象
Color color2 = colorFactory.getColor("Green");

//调用 Green 的 fill 方法
color2.fill();

//获取颜色为 Blue 的对象
Color color3 = colorFactory.getColor("BLUE");

//调用 Blue 的 fill 方法
color3.fill();
}
}

总结:抽象工厂相较于工厂而言,多了一个产品族的概念,实际上就是在工厂接口下再做一个细分。

单例

单例模式在多线程下的单例模式这篇文章已经有过初步介绍,这里详细列出:

  1. 懒汉式,线程不安全,懒加载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Singleton {  
    private static Singleton instance;
    private Singleton (){}

    public static Singleton getInstance() {
    if (instance == null) {
    instance = new Singleton();
    }
    return instance;
    }
    }
  2. 懒汉式,线程安全,懒加载

    加锁会影响效率。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Singleton {  
    private static Singleton instance;
    private Singleton (){}
    public static synchronized Singleton getInstance() {
    if (instance == null) {
    instance = new Singleton();
    }
    return instance;
    }
    }
  3. 饿汉式,线程安全,非懒加载常用

    没有加锁,执行效率会提高。

    类加载时就初始化,浪费内存:虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

    1
    2
    3
    4
    5
    6
    7
    public class Singleton {  
    private static Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
    return instance;
    }
    }
  4. 双重校验锁,线程安全,懒加载

    4和2比较起来,可以在保证线程安全的情况下依然保持高性能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Singleton {  
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getSingleton() {
    if (singleton == null) {
    synchronized (Singleton.class) {
    if (singleton == null) {
    singleton = new Singleton();
    }
    }
    }
    return singleton;
    }
    }
  5. 使用静态内部类,线程安全,懒加载

    这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Singleton {  
    private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){

    }
    public static final Singleton getInstance() {
    return SingletonHolder.INSTANCE;
    }
    }
  6. 使用枚举,线程安全,非懒加载

    这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。

    1
    2
    3
    4
    5
    public enum Singleton {  
    INSTANCE;
    public void whateverMethod() {
    }
    }

    总结一下:加锁降低执行效率,非懒加载产生垃圾对象,浪费内存。根据具体场景决定使用哪种方式。

建造者

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种模式的应用非常广泛,比如Java中的StringBuilder,Android中的AlertDialog.Builder。

用一个组装计算机的例子来说明。

先上图:这张图里的聚合和依赖关系标注有误,具体结合代码理解。

聚合关系(成员):如果A由B聚合成,表现为A包含有B的全局对象,但是B对象可以不在A创建的时刻创建。

组合关系(成员):如果A由B组成,表现为A包含有B的全局对象,并且B对象在A创建的时刻创建。

依赖关系(局部):如果A依赖于B,则B体现为局部变量,方法的参数、或静态方法的调用。

所以这里应该是Director由Builder组合而成,MacBookBuilder由MacBook组合而成。图上有误。

再上代码:

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
public class MacBook{ 
protected MacBook() {
}

public void setBoard(String board){
mBoard=board;
}

public void setDisplay(String display) {
this.mDisplay = display;
}
}

// 可以使用抽象类,也可以使用接口
public abstract class Builder {
abstract void buildBoard(String board);
abstract void buildDisplay(String display);
abstract MacBook build();
}

public class MacBookBuilder extends Builder {

private MacBook mMacBook = new MacBook();

@Override
void buildBoard(String board) {
mMacBook.setBoard(board);
}

@Override
void buildDisplay(String display) {
mMacBook.setDisplay(display);
}

@Override
Computer build() {
return mMacBook;
}
}

public class Director {
Builder mBuilder=null;

public Director(Builder builder) {
this.mBuilder = builder;
}

public void construct(String board,String display){
mBuilder.buildDisplay(display);
mBuilder.buildBoard(board);
}
}

// 具体使用
public class Test {

public static void main(String[] args){
// 定义每个建造环节的具体实现
Builder builder = new MacBookBuilder();

// 设计建造流程,将各个环节组装排序
Director director = new Director(builder);
director.construct("英特尔主板", "Retina显示器");

MacBook macBook = builder.build();
}

}

原型

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

用代码直观理解一下:

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
public abstract class Shape implements Cloneable {

private String id;
protected String type;

abstract void draw();

public String getType(){
return type;
}

public String getId() {
return id;
}

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

public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}

public class Rectangle extends Shape {

public Rectangle(){
type = "Rectangle";
}

@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}

public class Square extends Shape {

public Square(){
type = "Square";
}

@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}

public class Circle extends Shape {

public Circle(){
type = "Circle";
}

@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}

// 从数据库获取实体类,并把它们存储在一个 Hashtable 中。
public class ShapeCache {

private static Hashtable<String, Shape> shapeMap
= new Hashtable<String, Shape>();

public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}

// 对每种形状都运行数据库查询,并创建该形状
// shapeMap.put(shapeKey, shape);
// 例如,我们要添加三种形状
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);

Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(),square);

Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(),rectangle);
}
}

public class PrototypePatternDemo {
public static void main(String[] args) {
ShapeCache.loadCache();

Shape clonedShape = (Shape) ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.getType());

Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType());

Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
System.out.println("Shape : " + clonedShape3.getType());
}
}

注意:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。

微信公众号 长夜西风

个人网站 http://www.cmder.info/

书痴者文必工,艺痴者技必良。