引言
对于设计模式,应该明白不同的设计用来解决什么场景问题,对于常用的设计模式能够灵活运用。
设计模式分类
模式分类有助于更快地学习模式,并且对发现新的模式也有指导作用。
根据两条原则进行分类。
第一是目的准则,即模式是用来完成什么工作的。模式依据其目的分为创建型、结构型、行为型三种。
创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为型模式对类或对象怎样交互和怎样分配职责进行描述。
第二条是范围准则,指定模式主要是用于类还是用于对象。
类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了。
对象模式处理对象之间的关系,这些关系在运行时刻是可以变化的,更具动态性。
从某种意义上来说,几乎所有模式都是用继承机制,所以“类模式”只指那些集中于处理类间关系的模式,而大部分模式都属于对象模式的范畴。
类模式 |
对象模式 |
|
创建型 |
将对象的部分创建工作延迟到子类 |
将对象部分的创建工作延迟到另一个对象中 |
结构型 |
使用继承机制来组合类 |
描述了对象的组装方式 |
行为型 |
使用继承描述算法和控制流 |
描述一组对象怎样协作完成单个对象所无法完成的任务 |
还有其他组织模式的方式。有些模式经常会被绑在一起使用,有些模式是可替代的,有些模式尽管使用意图不同但产生的设计结果是很相似的。
还有一种方式是根据模式的“相关模式”部分所描述的他们怎样互相引用来组织设计模式。
显然存在着许多组织设计模式的方法。从多角度思考模式有助于对他们的功能、差异和应用场合的更深入理解。
创建型
- 单例模式
- 工厂方法模式
- 抽象工厂模式
- 建造者模式
- 原型模式
结构型
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
行为型
- 策略模式
- 模板方法模式
- 观察者模式
- 迭代子模式
- 责任链模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
下面重点介绍几种常用的设计模式。
常用设计模式
下面结合设计模式的实际应用,来介绍常用的设计模式,如下图所示。在面试时遇到类似问题,记得要将设计模式与实际业务场景进行结合,来体现对设计模式的理解和应用能力。
单例模式
首先是单例模式,这个模式在实际业务中会经常用到,也是设计模式中的主要考察点。这里介绍线程安全的单例模式实现方式。
单例模式常见的实现方式有三种。
-
第一种是静态初始化方式,也叫作饿汉方式。实现的思路就是在类初始化时完成单例实例的创建,因此不会产生并发问题,在这种方式下不管是否会使用到这个单例,都会创建这个单例。
-
第二种实现方式是双重检查,也叫作懒汉方式,只有在真正用到这个单例实例的时候才会去创建,如果没有使用就不会创建。这个方式必然会面对多个线程同时使用实例时的并发问题。为了解决并发访问问题,通过 synchronized 或者 lock 进行双重检查,保证只有一个线程能够创建实例。这里要注意内存可见性引起的并发问题,必须使用 volatile 关键字修饰单例变量。
-
第三种是单例注册表方式,Spring 中 Bean 的单例模式就是通过单例注册表方式实现的。
第二种双重检查, 代码示例 SingletonDoubleCheckedLocking.java
package com.xgcd.singletonPattern; /** * 双重锁/双检锁/双重校验锁 * <p> * 也是懒加载的方式 * 线程安全 * 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。 * getInstance() 的性能对应用程序很关键。 */ public class SingletonDoubleCheckedLocking { // 必须使用 volatile 关键字修饰单例变量 // 作用: 1.保证可见性。使用 volatile 定义的变量,将会保证对所有线程的可见性。 // 2.禁止指令重排序优化。 // 由于 volatile 禁止对象创建时指令之间重排序,所以其他线程不会访问到一个未初始化的对象,从而保证安全性。 // 注意,volatile禁止指令重排序在 JDK 5 之后才被修复 private static volatile SingletonDoubleCheckedLocking instance; private SingletonDoubleCheckedLocking() { } public static SingletonDoubleCheckedLocking getInstance() { // 多线程程序中,不用让每个线程每次都加锁,而只是在实例未被创建的时候再加锁处理.同时也能保证多线程的安全.这种做法被称为 double-checked locking(双重锁定) if (instance == null) { synchronized (SingletonDoubleCheckedLocking.class) { // 对于instance情况,直接返回instance // 当instance为null并且同时有两个线程调用getInstance方法时,他们将都可以通过第一重instance==null的判断 // 然后由于lock机制,这两个线程则只有一个进入,另一个在外排队等候,必须要其中的一个进入并出来后,另一个才能进入 // 而此时如果没有了第二重的instance==null的判断,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,这就没有达到单例的目的 // 所以采用两次instance==null的判断 if (instance == null) { instance = new SingletonDoubleCheckedLocking(); } } } return instance; } }
volatile关键字: 为什么双重检查锁模式需要 volatile ?
工厂模式
工厂模式是创建不同类型实例时常用的方式,例如 Spring 中的各种 Bean 是由不同 Bean 工厂类进行创建的。
代理模式
代理模式,主要用在不适合或者不能直接引用另一个对象的场景,可以通过代理模式对被代理对象的访问行为进行控制。Java 的代理模式分为静态代理和动态代理。静态代理指在编译时就已经创建好了代理类,例如在源代码中编写的类;动态代理指在 JVM 运行过程中动态创建的代理类,使用动态代理的方法有 JDK 动态代理、CGLIB、Javassist 等。面试时遇到这个问题可以举个动态代理的例子,比如在 Motan RPC 中,是使用 JDK 的动态代理,通过反射把远程请求进行封装,使服务看上去就像在使用本地的方法。
责任链模式
Chain of Responsibility 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系. 将这个对象连成一条链, 并沿着这条链传递该请求, 直到有一个对象处理它为止.
责任链模式有点像工厂的流水线,链上每一个节点完成对对象的某一种处理,例如 Netty 框架在处理消息时使用的 Pipeline 就是一种责任链模式。
适配器模式
适配器模式,类似于我们常见的转接头,把两种不匹配的对象来进行适配,也可以起到对两个不同的对象进行解藕的作用。例如我们常用的日志处理框架 SLF4J,如果我们使用了 SLF4J 就可以跟 Log4j 或者 Logback 等具体的日志实现框架进行解藕。通过不同适配器将 SLF4J 与 Log4j 等实现框架进行适配,完成日志功能的使用。
观察者模式
观察者模式也被称作发布订阅模式,适用于一个对象的某个行为需要触发一系列事件的场景,例如 gRPC 中的 Stream 流式请求的处理就是通过观察者模式实现的。
构造者模式
构造者模式,适用于一个对象有很多复杂的属性,需要根据不同情况创建不同的具体对象,例如创建一个 PB 对象时使用的 builder 方式。