设计模式(一)之创建型模式

一、OOP七大原则

1、开闭原则

对扩展开放,对修改关闭

​ 当应用需求发生改变时,尽量不修改原来代码功能块,可以在上面进行扩展

2、里氏替换原则

继承必须确保超类所拥有的性质在子类中仍然成立

​ 子类可以扩展父类功能,尽量不要修改原有父类的功能

3、依赖倒置原则

要面向接口编程,不要面向实现编程

​ 抽象不依赖细节,细节依赖于抽象

4、单一职责原则

控制类的粒度大小、将对象解耦、提高其内聚性

​ 一个方法尽量做一个事情

5、接口隔离原则

要为各个类建立它们需要的专用接口

6、迪米特法则

只与你的直接朋友交谈,不跟“陌生人”说话

​ A与B相关,B与C相关,A尽量不与C直接交流

7、合成复用原则

尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

二、设计模型

设计模式又称为GoF 23,是一种思维、一种态度、一种进步

设计模式主要分为三大类,每一类又分为多个小类

(1)创建型模式

​ 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式

(2)结构型模式

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

(3)行为型模式

​ 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式

三、创建型模式

1、单例模式

单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。

注意:

(1)单例类只能有一个实例

(2)单例类必须自己创建自己的唯一实例

(3)单例类必须给所有其他对象提供这一实例

1.1 饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
public class Hungry {
//构造器私有
private Hungry(){
}
//final 修饰对象,对象内容可变,引用地址不可变
//static 修饰变量,又属于静态变量,可以通过类名访问,无需创建对象访问
private final static Hungry HUNGRY = new Hungry();

public static Hungry getInstance(){
return HUNGRY;
}
}

缺点:在类加载时就初始化,浪费内存

1.2 懒汉式

1.2.1 线程不安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LazyMan {
//构造器私有
private LazyMan(){
}

private static LazyMan lazyMan;

public static LazyMan getInstance(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}

缺点:线程不安全,不支持多线程操作

修改上述代码,然后进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LazyMan {

//构造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":ok!");
}

private static LazyMan lazyMan;

public static LazyMan getInstance(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
//多线程并发
public static void main(String[] args){
for(int i = 0;i < 10 ;i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}

结果1

1
2
3
Thread-0:ok!
Thread-2:ok!
Thread-1:ok!

结果2

1
2
3
4
5
Thread-0:ok!
Thread-3:ok!
Thread-2:ok!
Thread-1:ok!
Thread-4:ok!

分析:

​ 从运行结果来看,每次运行时,输出结果都不一致,而实际想要的应该只输出一个线程名才对。因为在加载时,构造器私有应该只被执行一次才对,所以,该单例模式在多线程下是不安全的。

1.2.2 线程安全(synchronized锁方法)

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
public class LazyMan {

//构造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":ok!");
}

private static LazyMan lazyMan;

//synchronized锁方法
public static synchronized LazyMan getInstance(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
//多线程并发
public static void main(String[] args){
for(int i = 0;i < 10 ;i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}

结果(多次运行):

1
Thread-0:ok!

缺点:当getInstance()的方法使用比较频繁时,添加synchronized锁会严重影响性能

1.2.3 线程安全(双重检验锁,DCL:Double-Checked locking)

(1)常规的加synchronized锁,仍然存在多线程下的不安全性

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
// 不完整的双重检验锁
public class LazyMan {

//构造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":ok!");
}

private static LazyMan lazyMan;

// 双重检测锁 的懒汉式单例 DCL
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args){
for(int i = 0;i < 10 ;i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}

结果(多次运行),但是仍然是不安全的:

1
Thread-0:ok!

分析:

​ synchronized使用的是类锁,类的对象实例可以有多个,但是每一个类只有一个class对象。但类锁只是一个概念上的东西,并不是真实存在的。

(2)加volatile锁,禁止指令重排,保证多线程下的安全性

分析:

​ 上述代码存在什么问题?

     主要是该代码,在多线程下存在问题:
1
2
3
4
5
6
lazyMan = new LazyMan();//不是一个原子性操作
/*
1、分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
*/

​ 当一个线程new一个对象过程时,我们希望该线程按照123的步骤执行,但是在CPU内部,不影响单线程的结果的情况下,是会发生指令重排的事情,因此CPU的内部结果执行顺序很有可能是132。

​ 即线程A的执行步骤可能是132,但在线程A还未完成构造(2)时,线程B执行getInstance()操作,发现该对象已经指向某一个空间,线程B会返回lazyMan对象,但lazyMan还没有完成构造。

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
public class LazyMan {

//构造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":ok!");
}

//volatile关键字 禁止指令重排
private volatile static LazyMan lazyMan;

// 双重检测锁 的懒汉式单例 DCL
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args){
for(int i = 0;i < 10 ;i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}

1.2.4 静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Holder{
//构造器私有
private Holder(){

}

public statuic Holder getInstance(){
return InnerClass.HOLDER;
}

public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}

1.3 反射破坏单例模式

1.3.1 反射破坏单例

(1)反射破坏单例

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
import org.springframework.context.annotation.Lazy;

import java.lang.reflect.Constructor;

public class LazyMan {

//构造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName()+":ok!");
}

//有参构造函数
private LazyMan(Integer hello){
System.out.println(Thread.currentThread().getName()+hello+":hello!");
}

//volatile关键字 禁止指令重排
private volatile static LazyMan lazyMan;

// 双重检测锁 的懒汉式单例 DCL
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}

//反射破坏单例模式
public static void main(String[] args) throws Exception{
LazyMan instance = LazyMan.getInstance();//创建一个类对象
//获取LazyMan的无参构造函数-----(1)获取无参构造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
System.out.println("declaredConstructor构造器签名:"+declaredConstructor.toString());
//获取LazyMan的有参构造函数(这里要传的是类名)
Constructor<LazyMan> declaredConstructor1 = LazyMan.class.getDeclaredConstructor(Integer.class);
//修改访问权限-----(2)修改构造方法的访问权限
declaredConstructor.setAccessible(true);
//调用构造方法-----(3)调用构造方法,创建LazyMan的对象实例
LazyMan instance2 = declaredConstructor.newInstance();
LazyMan instance3 = declaredConstructor1.newInstance(2);
System.out.println(instance);
System.out.println(instance2);
System.out.println(instance3);
}
}

输出结果:

1
2
3
4
5
6
7
main:ok!
declaredConstructor构造器签名:private com.ldg.GoF23.single.LazyMan()
main:ok!
main2:hello!
com.ldg.GoF23.single.LazyMan@2a84aee7
com.ldg.GoF23.single.LazyMan@a09ee92
com.ldg.GoF23.single.LazyMan@30f39991

分析:

​ (1)LazyMan.class.getDeclaredConstructor需要抛出异常,所以添加了throws Exception

​ (2)LazyMan.class.getDeclaredConstructor存在有参和无参的情况,有参函数的需要用装箱表示,如Integer;在使用构造进行传参时,要传Integer.class;

​ (3)三个instance的输出不一致,就表示单例模式被破坏

1.3.2 单例防止反射破坏

对私有构造器添加synchronized类锁,防止反射破坏单例

双重检验锁升级为了三重检测锁

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
import java.lang.reflect.Constructor;

public class LazyMan {

//构造器私有
private LazyMan(){
// 添加synchronized锁,防止反射破坏单例
synchronized (LazyMan.class){
if(lazyMan != null){
throw new RuntimeException("不要试图使用反射破坏异常!");
}
}

}

//有参构造函数
private LazyMan(Integer hello){
System.out.println(Thread.currentThread().getName()+hello+":hello!");
}

//volatile关键字 禁止指令重排
private volatile static LazyMan lazyMan;

// 双重检测锁 的懒汉式单例 DCL
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}

//反射破坏单例模式
public static void main(String[] args) throws Exception{
LazyMan instance = LazyMan.getInstance();//创建一个类对象
//获取LazyMan的无参构造函数-----(1)获取无参构造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
System.out.println("declaredConstructor构造器签名:"+declaredConstructor.toString());
//获取LazyMan的有参构造函数(这里要传的是类名)
Constructor<LazyMan> declaredConstructor1 = LazyMan.class.getDeclaredConstructor(Integer.class);
//修改访问权限-----(2)修改构造方法的访问权限
declaredConstructor.setAccessible(true);
//调用构造方法-----(3)调用构造方法,创建LazyMan的对象实例
LazyMan instance2 = declaredConstructor.newInstance();
LazyMan instance3 = declaredConstructor1.newInstance(2);
System.out.println(instance);
System.out.println(instance2);
System.out.println(instance3);
}
}

运行结果:

image-20210531231607722

分析:

​ 三重检验锁的第一个instance实例是通过类的getInstance()获取的,若完全不用类进行获取,三重检验锁是否仍然可以防止反射破坏呢?

1.3.3 反射创建对象破坏三重检验锁单例

不通过 LazyMan.getInstance()获取实例,而直接用反射获取实例,结果又不一致

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
import java.lang.reflect.Constructor;
public class LazyMan {

//构造器私有
private LazyMan(){
// 添加synchronized锁,防止反射破坏单例
synchronized (LazyMan.class){
if(lazyMan != null){
throw new RuntimeException("不要试图使用反射破坏异常!");
}
}

}

//有参构造函数
private LazyMan(Integer hello){
System.out.println(Thread.currentThread().getName()+hello+":hello!");
}

//volatile关键字 禁止指令重排
private volatile static LazyMan lazyMan;

// 双重检测锁 的懒汉式单例 DCL
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}

//反射破坏单例模式
public static void main(String[] args) throws Exception{
//LazyMan instance = LazyMan.getInstance();//创建一个类对象
//获取LazyMan的无参构造函数-----(1)获取无参构造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
System.out.println("declaredConstructor构造器签名:"+declaredConstructor.toString());
//获取LazyMan的有参构造函数(这里要传的是类名)
Constructor<LazyMan> declaredConstructor1 = LazyMan.class.getDeclaredConstructor(Integer.class);
//修改访问权限-----(2)修改构造方法的访问权限
declaredConstructor.setAccessible(true);
//调用构造方法-----(3)调用构造方法,创建LazyMan的对象实例
LazyMan instance = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
LazyMan instance3 = declaredConstructor1.newInstance(2);
System.out.println(instance);
System.out.println(instance2);
System.out.println(instance3);
}
}

结果:

1
2
3
4
5
declaredConstructor构造器签名:private single.LazyMan()
main2:hello!
single.LazyMan@1b6d3586
single.LazyMan@4554617c
single.LazyMan@74a14482

1.3.4 红绿灯方法防止反射对象破坏单例

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
import java.lang.reflect.Constructor;

public class LazyMan {

private static boolean ldg = false;
//构造器私有
private LazyMan(){
// 添加synchronized锁,防止反射破坏单例
synchronized (LazyMan.class){
if(ldg == false){
ldg = true;
}else{
throw new RuntimeException("不要试图使用反射破坏异常!");
}
}
}

//有参构造函数
private LazyMan(Integer hello){
System.out.println(Thread.currentThread().getName()+hello+":hello!");
}

//volatile关键字 禁止指令重排
private volatile static LazyMan lazyMan;

// 双重检测锁 的懒汉式单例 DCL
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}

//反射破坏单例模式
public static void main(String[] args) throws Exception{
//LazyMan instance = LazyMan.getInstance();//创建一个类对象
//获取LazyMan的无参构造函数-----(1)获取无参构造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
System.out.println("declaredConstructor构造器签名:"+declaredConstructor.toString());
//获取LazyMan的有参构造函数(这里要传的是类名)
Constructor<LazyMan> declaredConstructor1 = LazyMan.class.getDeclaredConstructor(Integer.class);
//修改访问权限-----(2)修改构造方法的访问权限
declaredConstructor.setAccessible(true);
//调用构造方法-----(3)调用构造方法,创建LazyMan的对象实例
LazyMan instance = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
LazyMan instance3 = declaredConstructor1.newInstance(2);
System.out.println(instance);
System.out.println(instance2);
System.out.println(instance3);
}
}

结果:如下图所示,即使使用构造类创建的对象,通过这种红绿灯方式,也可以避免其破坏单例

image-20210531234405245

1.3.5 反编译破解红绿灯方法,破坏单例

假设代码被反编译,能找到其中的属性值:ldg,并对其进行破坏,是否仍然可以破坏单例呢?

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
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class LazyMan {

private static boolean ldg = false;
//构造器私有
private LazyMan(){
// 添加synchronized锁,防止反射破坏单例
synchronized (LazyMan.class){
if(ldg == false){
ldg = true;
}else{
throw new RuntimeException("不要试图使用反射破坏异常!");
}
}
}

//有参构造函数
private LazyMan(Integer hello){
System.out.println(Thread.currentThread().getName()+hello+":hello!");
}

//volatile关键字 禁止指令重排
private volatile static LazyMan lazyMan;

// 双重检测锁 的懒汉式单例 DCL
public static LazyMan getInstance(){
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}

//反射破坏单例模式
public static void main(String[] args) throws Exception{
//LazyMan instance = LazyMan.getInstance();//创建一个类对象

Field ldg = LazyMan.class.getDeclaredField("ldg");//假设已知ldg的字段名
ldg.setAccessible(true);//破坏其私有权限
//获取LazyMan的无参构造函数-----(1)获取无参构造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
System.out.println("declaredConstructor构造器签名:"+declaredConstructor.toString());
//获取LazyMan的有参构造函数(这里要传的是类名)
Constructor<LazyMan> declaredConstructor1 = LazyMan.class.getDeclaredConstructor(Integer.class);
//修改访问权限-----(2)修改构造方法的访问权限
declaredConstructor.setAccessible(true);
//调用构造方法-----(3)调用构造方法,创建LazyMan的对象实例
LazyMan instance = declaredConstructor.newInstance();

ldg.set(instance,false);//将第一个对象的值改为false
LazyMan instance2 = declaredConstructor.newInstance();
LazyMan instance3 = declaredConstructor1.newInstance(2);
System.out.println(instance);
System.out.println(instance2);
System.out.println(instance3);
}
}

结果:如下所示,单例又被破坏

    declaredConstructor构造器签名:private single.LazyMan()
    main2:hello!
    single.LazyMan@4554617c
    single.LazyMan@74a14482
    single.LazyMan@1540e19d

1.3.6 源码分析

通过对newInstance()的源码分析,发现反射不能破坏枚举类型

image-20210531235452051

(1)在IDEA工具中,查看class源码,发现枚举类的构造函数是无参构造

image-20210604222914414

(2)先获取一个枚举类,然后用反射实例化一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

// enum 是什么?本身也是一个Class类
public enum EnumSingle {
INSATNCE;
public EnumSingle getInsatnce(){
return INSATNCE;
}
}

class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance = EnumSingle.INSATNCE;
//使用无参构造函数
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}

结果:

​ 如下所示,异常说明,类中没有该构造器方法,但Idea中的class类显示为无参构造,且报错信息与源码中的破坏信息不一致,因此需要进一步深究其原因。

1
2
3
4
Exception in thread "main" java.lang.NoSuchMethodException: single.EnumSingle.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at single.Test.main(EnumSingle.java:28)

(3)打开build后的class类源文件所在位置,并在该目录下,打开cmd窗口

1
javap -p EnumSingle.class

从EnumSingle的源码中,可以看到,枚举是一个class类,继承一个类,且其构造参数也是无参构造参数,但是程序的结果却和我们想要的结果:Cannot reflectively create enum objects 不一致,因此需要使用专有的反编译工具

image-20210604224341255

(4)使用jad反编译工具,反编译类(jad下载

​ 将下载好的jad.exe和class文件放在同一层级,并使用如下代码反编译类

1
jad -sjava EnumSingle.class

image-20210604225217110

​ 查看反编译后的java文件,发现其参数是有参构造函数

image-20210604225349433

(5)修改反射代码中的无参为有参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}

class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);//有参构造函数
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}

结果:

​ 该结果才与最初始的源码错误保持一致,且证明了反射不能破坏枚举的单例类型

1
2
3
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.ldg.GoF23.single.Test.main(EnumSingle.java:18)

参考

菜鸟教程单例模式

0%