设计模式学习

单例模式

单例模式 ( Singleton pattern ) 确保一个类只有一个实例,并提供全局访问点。在 Java 中实现单例模式需要私有的构造器、一个静态方法和一个静态变量。

一般在以下两种场景中会考虑使用单例模式:

  • 产生某对象会消耗过多的资源,为避免频繁地创建与销毁对象对资源的浪费。如:对数据库的操作、访问 IO、线程池、网络请求等。

  • 某种类型的对象应该有且只有一个。如果制造出多个这样的实例,可能导致:程序行为异常、资源使用过量、结果不一致等问题。比如一个系统只能有:一个窗口管理器或文件系统等。

单例模式按加载时机可以分为:懒汉式和饿汉式;按实现的方式则可以分为:双重检查锁,内部类方式和枚举方式等等。

懒汉式

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static Singleton singleton;
private Singleton(){
}
public static Singleton getInstance() {
if (instance == null) {
singleton = new Singleton();
}
return singleton;
}
}

优点:Lazy loading,第一次调用才初始化,避免了内存浪费。

缺点:只有第一次执行方法时才真正需要同步。即 uniqueSingleton 被初始化后的每次调用该方法,同步都是多余的,效率过低。非线程安全。

饿汉式

1
2
3
4
5
6
7
8
9
10
public class Singleton {
// 类加载时就进行初始化,实现了线程安全
// 因为JVM保证在任何线程访问 uniqueSingleton 前就已经创建了实例
private static Singleton singleton = new Singleton();
private Singleton(){
}
public static Singleton getInstance() {
return singleton;
}
}

优点:没有加锁,而是基于 classloader 机制解决了多线程下的同步问题,执行效率会提高,线程安全。
缺点:不是 Lazy loading,无论方法是否被调用,类加载时就进行了初始化实例,如果该实例从始至终都没被使用过,会浪费内存。

双重检查锁 ( Double-checked locking )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public class Singleton {
// 使用 volatile 关键字以确保多个线程能够正确处理 uniqueSingleton
// 不然会有重排序问题
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
// 先判断对象是否已经被初始化,再决定要不要加锁
if (null == singleton) {
// 只有第一次初始化,才会进行同步
synchronized (Singleton.class) {
// 再次检查变量是否已经被初始化
if (null == singleton) {
// 如果还没被初始化就初始化一个对象
singleton = new Singleton();
}
}
}
return singleton;
}
}

优点:Lazy loading,采用双锁机制,线程安全且在多线程情况下能保持高性能。

静态内部类

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

这种方式与饿汉式一样,利用了 classloder 的机制来保证线程安全。但这种方式下 Singleton 类被装载了,INSTANCE 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。

优点:Lazy loading

枚举

不幸的是,以上四种方式都有一个致命的缺点——无法抵御序列化与反射攻击。Josh Bloch 在《Effective Java》中指出“单元素的枚举类型是实现单例模式的最佳方法”,它更简洁,自动支持序列化机制,绝对防止多次实例化,且能够抵御序列化与反射攻击。

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

缺点:不是 Lazy loading

工厂模式

工厂模式 (Factory Pattern) 定义了一个创建对象的借口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。在工厂模式中,在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

简单 / 静态工厂

简单工厂模式属于创建型模式,又叫做静态工厂方法 (Static Factory Method) 模式,不属于 23 种 GoF 设计模式。简单工厂模式由一个工厂对象决定创建出哪一种产品类的实例,是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。

《Effective Java》中第一节就指出“使用静态工厂方法代替构造器”,并列举了静态工厂方法相较构造器的四大优势:有名称、对象可复用、可返回任何子类型对象、减少了创建参数化类型实例时的代码量。

但静态工厂方法也存在缺点:

  1. 类如果不含公有的或者受保护的构造器,就不能被子类化。

  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
// 创建一个接口
public interface Shape {
void draw();
}

// 创建实现接口的实体类


// 正方形
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Draw square.");
}
}

// 圆形
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Draw circle.");
}
}

// 创建一个简单工厂,生成基于给定信息的实体类的对象
public class ShapeFactory {
// 使用 getShape 方法获取形状类型的对象
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}

// 使用该工厂,通过传递类型信息来获取实体类的对象
public class SimpleFactoryDemo {
public static void main(String[] args) {
// 新建工厂
ShapeFactory shapeFactory = new ShapeFactory();
//获取 Circle 对象,并调用 draw 方法
Shape shape = shapeFactory.getShape("CIRCLE");
shape.draw();
}
}

当需求增加,不满足于之生产正方形、圆形,还需要三角形的时候,还需要:

  1. 添加一个三角形类,添加三角形类并不会影响到正方形类、圆形类。这符合开闭原则(扩展开放,修改关闭)。

  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
// 创建一个接口
public interface Shape {
void draw();
}


// 正方形
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Draw square.");
}
}


// 圆形
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Draw circle.");
}
}


// 创建一个工厂类的接口,让不同的形状子类去实现它
public interface ShapeFactory {
public Shape produce();
}


// 正方形工厂
class SquareFactory implements ShapeFactory {
@Override
public Shape getShape() {
return new Square();
}
}

// 圆形工厂
class CircleFactory implements ShapeFactory {
@Override
public Shape getShape() {
return new Circle();
}
}


// 通过新建不同的具体工厂来创建不同的形状
public class SimpleFactoryDemo {
public static void main(String[] args) {
// 新建一个正方形工厂
ShapeFactory squareFactory = new SquareFactory;
//获取 Square 对象,并调用 draw 方法
Shape shape = squareFactory.getShape();
shape.draw();
}
}

抽象工厂

如果还不满足于生产形状,还需要增加生产颜色产品时,可以引入抽象工厂。

抽象工厂为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。这一组相关或者相互依赖的对象有一个名词来形容——产品族。

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
// 创建一个接口
public interface Shape {
void draw();
}

// 创建实现接口的实体类

// 正方形
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Draw square.");
}
}


// 圆形
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Draw circle.");
}
}


// 为颜色创建一个接口
public interface Color {
void fill();
}


// 创建实现接口的实体类

// 红色
public class Red implements Color {
@Override
public void fill() {
System.out.println("Fill Red.");
}
}


// 蓝色
public class Blue implements Color {
@Override
public void fill() {
System.out.println("Fill Blue.");
}
}



// 为 Color 和 Shape 对象创建抽象类来获取工厂
public abstract class AbstractFactory {
public abstract Color getColor(String color);
public abstract Shape getShape(String shape) ;
}



// 创建扩展了 AbstractFactory 的工厂类,基于给定的信息生成实体类的对象


// 形状工厂
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("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("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;
}
}



// 使用 FactoryProducer 来获取 AbstractFactory,通过传递类型信息来获取实体类的对象。
public class AbstractFactoryPatternDemo {
public static void main(String[] args) {
// 获取形状工厂
AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
// 获取形状为 Circle 的对象
Shape shape = shapeFactory.getShape("CIRCLE");
// 调用 Circle 的 draw 方法
shape.draw();

// 获取颜色工厂
AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
// 获取颜色为 Red 的对象
Color color = colorFactory.getColor("RED");
// 调用 Red 的 fill 方法
color.fill();
}

}

反射实现简单工厂

利用反射实现简单工厂,不需要改动工厂类内的代码,同样实现了“开闭原则”。

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
// 创建一个接口
public interface Shape {
void draw();
}


// 创建实现接口的实体类

// 正方形
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Draw square.");
}
}

// 圆形
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Draw circle.");
}
}


// 创建一个简单工厂,利用反射生成对象,当新增一个产品类时,工厂类就不需要做任何改动了
public class ShapeFactory {
// 使用 getShape 方法获取形状类型的对象
public Shape getShape(String className){
return (Shape) Class.forName(className).newInstance();
}
}

public class SimpleFactoryDemo {
public static void main(String[] args) {
// 新建工厂
ShapeFactory shapeFactory = new ShapeFactory();
// 通过工厂类取得接口实例,传入完整的包.类名称
Shape shape = shapeFactory.getShape("com.marticles.shapefactory");
shape.draw();
}
}

代理模式

代理模式( Proxy pattern )为某个对象提供一个替身(代理对象),并由代理对象控制对原对象的引用。

代理模式可分为静态代理与动态代理,动态代理又可分为JDK动态代理与CGLib动态代理。

动态代理和静态代理的区别:

  • 静态代理在编译时就已经实现,编译完成后代理类class文件就已被创建。

  • 动态代理是在程序运行时通过反射机制动态生成的,编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中。

静态代理

1
2
3
4
// 创建一个接口
public interface Subject {
void doSomething();
}
1
2
3
4
5
6
7
// 创建一个实现该接口的类
public class RealSubject implements Subject{
@Override
public void doSomething(){
System.out.println("Do something ....");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 代理类
public class ProxySubject implements Subject{
private RealSubject realSubject;

public ProxySubject(RealSubject realSubject){
this.realSubject = realSubject;
}

@Override
public void doSomething(){
// 这里进行了增强
System.out.println("Before do something ....");
realSubject.doSomething();
System.out.println("After do something ....");
}
}
1
2
3
4
5
6
// 测试
public static void main(String[] args){
RealSubject realSubject = new RealSubject();
ProxySubject proxySubject = new ProxySubject(realSubject);
proxySubject.doSomething();
}

未完待续

  • 本文作者: Marticles
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!