设计模式梳理

总参考资料

设计模式分类

Reference

  1. 创建型模式:主要用于处理对象的创建,实例化对象;
  2. 结构型模式:处理类或对象间的组合。
  3. 行为型模式:描述类或对象怎么进行交互和职责分配;

设计模式的七大原则

  1. 单一职责原则:一个类负责一项职责;
  2. 里氏替换原则:子类扩展而非修改父类功能;
  3. 依赖倒置原则:面向接口编程,尽量依赖上层抽象,而非依赖具体;
  4. 接口隔离原则:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少;
  5. 迪米特法则:类间解耦;
  6. 开闭原则:尽量通过扩展代码解决需求变化,而非修改之前代码;
  7. 组合复用原则:尽量使用组合少使用继承的关系来达到复用的原则。

单例模式

什么是单例模式

单例模式就是确保某一个类只有一个实例,并且提供一个全局访问点(static修饰一个变量)。

单例模式的使用场景

线程池的设计采用的就是单例模式,只有一个线程池实例方便对多线程的管理。

单例模式的优缺点及注意事项

优点

对单例类的实例化都得到同一个实例,系统内存只存了单例类的一个对象,节约了系统资源;提供了共享资源的唯一实例的受控访问;

单例模式的实现方式

饿汉式

初试化时就创建单例,实现方式为static修饰一个变量,指向new的一个单例对象;

枚举类

初试化时就创建单例,最佳实践。可以避免反序列化新建对象实例,和反射攻击。

Java在序列化枚举类时,只是将name属性输出到结果中,反序列化时通过java.lang.EnumvalueOf方法根据name查找对象,因此枚举类在序列化后还是单例的;

Enum是一个抽象类,不能实例化。因此在反射时,不能通过 setAccessible()将private的构造方法改为public,继而反复实例化对象。

Reference

懒汉式(线程不安全)

延迟初始化,在使用的时候再调用方法进行单例对象的初始化,如果不加锁,会导致线程不安全。

同步锁(懒汉式线程安全)

对整个调用的静态方法加锁(synchronized),锁的是整个类,实现单例对象的初始化操作;

双重校验锁

在静态方法的内部,对初始化代码块进行加锁(synchronized),第一次判断是对单例变量进行判断,如果没有初始化进行初始化;第二次判断是加锁后再次进行判断,因为多线程执行时,对于

静态内部类

当单例对象的类加载时,并没有对其进行实例化。而在调用其静态内部类里的属性或者方法时,jvm才会动态加载,得到实例对象。

各种实现方式的比较

实现方式 原理 优点 缺点 应用场景 备注
饿汉式 使用jvm类加载机制,保证单例只被创建一次 线程安全;初始化速度快;占用内存小 单例创建时机不可控 初始化时就需要创建单例;要求初始化速度快,占用内存小。
枚举类型 final不可被继承;枚举元素是类静态常量,依赖jvm加载,只创建一次; 线程安全;自由序列化;实现简单 单例创建时机不可控
懒汉式 类加载时,先不加载单例,需要通过手动创建单例 按需加载单例;节约资源 线程不安全 需要按需,延迟创建单例;单例初始化的操作耗时长,要求启动速度快;
同步锁 使用同步锁保证手动创建单例时是线程安全的 按需加载单例;节约资源;同时线程安全 有同步的资源开销
双重校验锁 锁1: 若单例已创建直接返回,无需加锁;锁2: 防止多次创建单例 线程安全;节省资源(不需要过多的同步开销) 实现复杂;jvm会对指令重排序,导致可能初始化多次。
静态内部类 按需加载:在程序实例化内部类时才会创建单例;由jvm加载,类只会加载一遍,可以保证只有一个单例。 线程安全;节省资源(不需要过多的同步开销);实现简单。

各实现方式的具体实现

  • 懒汉式

    1
    private static Singleton uniqueInstance = new Singleton();
  • 枚举类

    1
    2
    3
    public enum Singleton {
    uniqueInstance;
    }
  • 懒汉式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public 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
    18
    public 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
    13
    public 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生成动态代理的步骤

  1. 定义一个接口类
  2. 定义一个实现类实现接口类的方法
  3. 定义一个子类实现InvocationHandler接口,重写``invoke方法,在invoke`方法植入代理对象的扩展功能;
  4. 在上层函数里,通过调用静态方法Proxy.newProxyInstance传入接口类的类加载器,被代理的接口类列表,InvocationHandler子类的实例,可以创建出代理对象。
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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
// 1. 定义一个接口类
interface Iservice {
void m1();
void m2();
void m3();
}
// 2. 接口的实现类
class MyServce implements Iservice{
@Override
public void m1() {
System.out.println("method1 executed.");
}
@Override
public void m2() {
System.out.println("method2 executed.");
}
@Override
public void m3() {
System.out.println("method3 executed.");
}
}
// 3. 自定义的InvocationHandler的子类,重写invoke方法(当然也可以在调用的时候使用匿名类重写方法)
class MyInvocationHandler implements InvocationHandler {
private Object target;
// 通过构造方法传入被代理类的实例
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + " start");
// 通过调用method的invoke方法,执行被代理类方法
method.invoke(target, args);
System.out.println(method.getName() + " end");
return null;
}
}
public void method() {
// 4. 通过Proxy的静态方法newProxyInstance,传入接口类的类加载器,接口类列表,InvocationHandler实例,得到一个与接口相同类型的代理对象
Iservice proxyIservice = (Iservice) Proxy.newProxyInstance(Iservice.class.getClassLoader(), new Class[]{Iservice.class}, new MyInvocationHandler(new MyServce()));
// 5. 调用接口方法,执行代理对象
proxyIservice.m1();
proxyIservice.m2();
proxyIservice.m3();·
}
public static void main(String[] args) throws Exception {
Test test = new Test();
test.method();
}
// output:
// m1 start
// method1 executed.
// m1 end
// m2 start
// method2 executed.
// m2 end
// m3 start
// method3 executed.
// m3 end
}

实现方式

利用拦截器(拦截器必须实现``InvocationHanlder`)加上反射机制生成一个实现代理接口的匿名类,

在调用具体方法前调用InvokeHandler来处理。

实现原理

https://blog.csdn.net/yhl_jxy/article/details/80586785

CGLib动态代理模式

使用CGLib生成动态代理的步骤

  1. 定义一个子类实现MethodInterceptor接口;
  2. 定义一个创建代理对象实例的方法,在方法里:创建一个enhancer实例,把代理对象的类信息及当前对象注入到enhancer属性里,调用create方法创建一个代理类的实例;
  3. 通过重写intercept方法,编写扩展功能;
  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
57
58
59
60
61
62
63
64
65
import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class Test {

class MyCGLibProxy implements MethodInterceptor {

private Object target;

public Object getInstance(Object target) {
this.target = target;
// enhancer对象的这些方法是在干嘛?
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return (Object) enhancer.create();
}

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable{
System.out.println("intercept start");
// 通过method.invoke方法调用被代理对象方法
method.invoke(target, args);
System.out.println("intercept end");
return null;
}
}

class MyServiceImpl {
public void m1() {
System.out.println("method1 executing");
}
public void m2() {
System.out.println("method2 executing");
}
}

void method() {
// 创建被代理对象实例
MyServiceImpl myService = new MyServiceImpl();
// 创建代理对象
MyCGLibProxy proxy = new MyCGLibProxy();
// 通过传入代理对象获得被代理类实例
Object ob = proxy.getInstance(myService);
MyServiceImpl myServiceObject = (MyServiceImpl) ob;
// 执行代理对象方法
myServiceObject.m1();
myServiceObject.m2();
}


public static void main(String[] args) throws Exception {
Test test = new Test();
test.method();
}
// intercept start
// method1 executing
// intercept end
// intercept start
// method2 executing
// intercept end

}

实现方式

利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

实现原理

JDK和CGLIB的区别

JDK动态代理只能对实现了接口的类生成代理,而不能针对类。JDK只能为接口创建代理类。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。

JDK动态代理 CGLib动态代理
代理对象 基于接口,有对应的实现类 直接基于类
优点 基于JDK原生,不需要依赖第三方;通过反射生成代理类比操作字节码速度要快。 不需要基于接口实现,方法的执行效率要比JDK动态代理高
缺点 被代理的类必须是接口的实现类;执行代理方法的时候,通过反射机制回调,方法的执行效率更低。 CGLIB代理类使用的是继承,因此不能对private,final修饰的类进行代理;生成代理类速度比JDK慢。
原理 基于JDK原生包,通过反射实现 通过操作字节码实现

Spring AOP 动态代理的模式切换

模版方法模式

AQS设计模式