一、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 | public class Hungry { |
缺点:在类加载时就初始化,浪费内存
1.2 懒汉式
1.2.1 线程不安全
1 | public class LazyMan { |
缺点:线程不安全,不支持多线程操作
修改上述代码,然后进行测试
1 | public class LazyMan { |
结果1
1 | Thread-0:ok! |
结果2
1 | Thread-0:ok! |
分析:
从运行结果来看,每次运行时,输出结果都不一致,而实际想要的应该只输出一个线程名才对。因为在加载时,构造器私有应该只被执行一次才对,所以,该单例模式在多线程下是不安全的。
1.2.2 线程安全(synchronized锁方法)
1 | public class LazyMan { |
结果(多次运行):
1 Thread-0:ok!缺点:当getInstance()的方法使用比较频繁时,添加synchronized锁会严重影响性能
1.2.3 线程安全(双重检验锁,DCL:Double-Checked locking)
(1)常规的加synchronized锁,仍然存在多线程下的不安全性
1 | // 不完整的双重检验锁 |
结果(多次运行),但是仍然是不安全的:
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 | public class LazyMan { |
1.2.4 静态内部类
1 | public class Holder{ |
1.3 反射破坏单例模式
1.3.1 反射破坏单例
(1)反射破坏单例
1 | import org.springframework.context.annotation.Lazy; |
输出结果:
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
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 | import java.lang.reflect.Constructor; |
运行结果:
分析:
三重检验锁的第一个instance实例是通过类的getInstance()获取的,若完全不用类进行获取,三重检验锁是否仍然可以防止反射破坏呢?
1.3.3 反射创建对象破坏三重检验锁单例
不通过 LazyMan.getInstance()获取实例,而直接用反射获取实例,结果又不一致
1 | import java.lang.reflect.Constructor; |
结果:
1
2
3
4
5 declaredConstructor构造器签名:private single.LazyMan()
main2:hello!
single.LazyMan@1b6d3586
single.LazyMan@4554617c
single.LazyMan@74a14482
1.3.4 红绿灯方法防止反射对象破坏单例
1 | import java.lang.reflect.Constructor; |
结果:如下图所示,即使使用构造类创建的对象,通过这种红绿灯方式,也可以避免其破坏单例
1.3.5 反编译破解红绿灯方法,破坏单例
假设代码被反编译,能找到其中的属性值:ldg,并对其进行破坏,是否仍然可以破坏单例呢?
1 | import java.lang.reflect.Constructor; |
结果:如下所示,单例又被破坏
declaredConstructor构造器签名:private single.LazyMan() main2:hello! single.LazyMan@4554617c single.LazyMan@74a14482 single.LazyMan@1540e19d
1.3.6 源码分析
通过对newInstance()的源码分析,发现反射不能破坏枚举类型
(1)在IDEA工具中,查看class源码,发现枚举类的构造函数是无参构造
(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 不一致,因此需要使用专有的反编译工具
(4)使用jad反编译工具,反编译类(jad下载)
将下载好的jad.exe和class文件放在同一层级,并使用如下代码反编译类
1 jad -sjava EnumSingle.class 查看反编译后的java文件,发现其参数是有参构造函数
(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)