设计模式梳理
设计模式分类
- 创建型模式:主要用于处理对象的创建,实例化对象;
- 结构型模式:处理类或对象间的组合。
- 行为型模式:描述类或对象怎么进行交互和职责分配;
设计模式的七大原则
- 单一职责原则:一个类负责一项职责;
- 里氏替换原则:子类扩展而非修改父类功能;
- 依赖倒置原则:面向接口编程,尽量依赖上层抽象,而非依赖具体;
- 接口隔离原则:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少;
- 迪米特法则:类间解耦;
- 开闭原则:尽量通过扩展代码解决需求变化,而非修改之前代码;
- 组合复用原则:尽量使用组合少使用继承的关系来达到复用的原则。
单例模式
什么是单例模式
单例模式就是确保某一个类只有一个实例,并且提供一个全局访问点(static修饰一个变量)。
单例模式的使用场景
线程池的设计采用的就是单例模式,只有一个线程池实例方便对多线程的管理。
单例模式的优缺点及注意事项
优点
对单例类的实例化都得到同一个实例,系统内存只存了单例类的一个对象,节约了系统资源;提供了共享资源的唯一实例的受控访问;
单例模式的实现方式
饿汉式
初试化时就创建单例,实现方式为static修饰一个变量,指向new的一个单例对象;
枚举类
初试化时就创建单例,最佳实践。可以避免反序列化新建对象实例,和反射攻击。
Java在序列化枚举类时,只是将name属性输出到结果中,反序列化时通过
java.lang.Enum
的valueOf
方法根据name查找对象,因此枚举类在序列化后还是单例的;Enum是一个抽象类,不能实例化。因此在反射时,不能通过
setAccessible()
将private的构造方法改为public,继而反复实例化对象。
懒汉式(线程不安全)
延迟初始化,在使用的时候再调用方法进行单例对象的初始化,如果不加锁,会导致线程不安全。
同步锁(懒汉式线程安全)
对整个调用的静态方法加锁(synchronized),锁的是整个类,实现单例对象的初始化操作;
双重校验锁
在静态方法的内部,对初始化代码块进行加锁(synchronized),第一次判断是对单例变量进行判断,如果没有初始化进行初始化;第二次判断是加锁后再次进行判断,因为多线程执行时,对于
静态内部类
当单例对象的类加载时,并没有对其进行实例化。而在调用其静态内部类里的属性或者方法时,jvm才会动态加载,得到实例对象。
各种实现方式的比较
实现方式 | 原理 | 优点 | 缺点 | 应用场景 | 备注 |
---|---|---|---|---|---|
饿汉式 | 使用jvm类加载机制,保证单例只被创建一次 | 线程安全;初始化速度快;占用内存小 | 单例创建时机不可控 | 初始化时就需要创建单例;要求初始化速度快,占用内存小。 | |
枚举类型 | final不可被继承;枚举元素是类静态常量,依赖jvm加载,只创建一次; | 线程安全;自由序列化;实现简单 | 单例创建时机不可控 | ||
懒汉式 | 类加载时,先不加载单例,需要通过手动创建单例 | 按需加载单例;节约资源 | 线程不安全 | 需要按需,延迟创建单例;单例初始化的操作耗时长,要求启动速度快; | |
同步锁 | 使用同步锁保证手动创建单例时是线程安全的 | 按需加载单例;节约资源;同时线程安全 | 有同步的资源开销 | ||
双重校验锁 | 锁1: 若单例已创建直接返回,无需加锁;锁2: 防止多次创建单例 | 线程安全;节省资源(不需要过多的同步开销) | 实现复杂;jvm会对指令重排序,导致可能初始化多次。 | ||
静态内部类 | 按需加载:在程序实例化内部类时才会创建单例;由jvm加载,类只会加载一遍,可以保证只有一个单例。 | 线程安全;节省资源(不需要过多的同步开销);实现简单。 |
各实现方式的具体实现
懒汉式
1
private static Singleton uniqueInstance = new Singleton();
枚举类
1
2
3public enum Singleton {
uniqueInstance;
}懒汉式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
// 线程不安全
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// 线程安全
public static synchronized Singleton getUniqueInstance() { //就多了一个synchronized关键字
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}双重校验锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}静态内部类
1
2
3
4
5
6
7
8
9
10
11
12
13public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
工厂模式
什么是工厂模式
工厂模式提供了一种创建对象的最佳方式。在创建对象时不会对客户端暴露创建逻辑,并且通过使用一种共同的接口指向新创建的对象,实现了创建者和调用者的分离。
工厂模式的优势
用工厂方法代替new一个对象,对创建对象进行统一管理,可以降低程序的耦合性,为后期程序的维护提供便利。
工厂模式的分类
简单工厂模式
简单工厂模式是由一个核心类来创建工厂内的产品,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可,缺点在于当产品过多,核心类的创建工作不容易维护。
工厂方法模式
工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
抽象工厂模式
抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。
代理模式
什么是代理模式
代理模式是通过代理控制对象的访问,可以在对象调用方法前后进行额外的处理,同时可以在不改变原有业务流程的情况下,切入新的功能。
为什么要使用代理模式
代理模式的使用场景
Spring AOP、日志打印、异常处理、事务控制、权限控制等。
代理模式的分类
静态代理模式
代理类和委托类的关系在程序运行前就已经在程序写好了,而动态代理事程序发布后,动态地创建代理对象。
静态代理的缺点在于需要代理的对象自己重复编写代理。
JDK动态代理模式
使用JDK生成动态代理的步骤
- 定义一个接口类
- 定义一个实现类实现接口类的方法
- 定义一个子类实现
InvocationHandler
接口,重写``invoke方法,在
invoke`方法植入代理对象的扩展功能; - 在上层函数里,通过调用静态方法
Proxy.newProxyInstance
传入接口类的类加载器,被代理的接口类列表,InvocationHandler
子类的实例,可以创建出代理对象。
1 |
|
实现方式
利用拦截器(拦截器必须实现``InvocationHanlder`)加上反射机制生成一个实现代理接口的匿名类,
在调用具体方法前调用InvokeHandler
来处理。
实现原理
https://blog.csdn.net/yhl_jxy/article/details/80586785
CGLib动态代理模式
使用CGLib生成动态代理的步骤
- 定义一个子类实现
MethodInterceptor
接口; - 定义一个创建代理对象实例的方法,在方法里:创建一个
enhancer
实例,把代理对象的类信息及当前对象注入到enhancer
属性里,调用create
方法创建一个代理类的实例; - 通过重写
intercept
方法,编写扩展功能; - 在上层函数里分别创建代理类实例和类实例对象,通过调用创建代理类的方法,传入实例对象,就可以获得代理对象实例。
1 |
|
实现方式
利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
实现原理
JDK和CGLIB的区别
JDK动态代理只能对实现了接口的类生成代理,而不能针对类。JDK只能为接口创建代理类。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。
JDK动态代理 | CGLib动态代理 | |
---|---|---|
代理对象 | 基于接口,有对应的实现类 | 直接基于类 |
优点 | 基于JDK原生,不需要依赖第三方;通过反射生成代理类比操作字节码速度要快。 | 不需要基于接口实现,方法的执行效率要比JDK动态代理高 |
缺点 | 被代理的类必须是接口的实现类;执行代理方法的时候,通过反射机制回调,方法的执行效率更低。 | CGLIB代理类使用的是继承,因此不能对private,final修饰的类进行代理;生成代理类速度比JDK慢。 |
原理 | 基于JDK原生包,通过反射实现 | 通过操作字节码实现 |
Spring AOP 动态代理的模式切换
模版方法模式
AQS设计模式