设计模式分类及常用设计模式

引言 对于设计模式,应该明白不同的设计用来解决什么场景问题,对于常用的设计模式能够灵活运用。 设计模式分类 模式分类有助于更快地学习模式,并且对发现新的模式也

引言

对于设计模式,应该明白不同的设计用来解决什么场景问题,对于常用的设计模式能够灵活运用。

设计模式分类

模式分类有助于更快地学习模式,并且对发现新的模式也有指导作用。

根据两条原则进行分类。

第一是目的准则,即模式是用来完成什么工作的。模式依据其目的分为创建型、结构型、行为型三种。

创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为型模式对类或对象怎样交互和怎样分配职责进行描述。

第二条是范围准则,指定模式主要是用于类还是用于对象。

类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了。

对象模式处理对象之间的关系,这些关系在运行时刻是可以变化的,更具动态性。

从某种意义上来说,几乎所有模式都是用继承机制,所以“类模式”只指那些集中于处理类间关系的模式,而大部分模式都属于对象模式的范畴。

 

类模式

对象模式

创建型

将对象的部分创建工作延迟到子类

将对象部分的创建工作延迟到另一个对象中

结构型

使用继承机制来组合类

描述了对象的组装方式

行为型

使用继承描述算法和控制流

描述一组对象怎样协作完成单个对象所无法完成的任务

还有其他组织模式的方式。有些模式经常会被绑在一起使用,有些模式是可替代的,有些模式尽管使用意图不同但产生的设计结果是很相似的。

还有一种方式是根据模式的“相关模式”部分所描述的他们怎样互相引用来组织设计模式。

显然存在着许多组织设计模式的方法。从多角度思考模式有助于对他们的功能、差异和应用场合的更深入理解。

 

 

 

创建型

  1. 单例模式
  2. 工厂方法模式
  3. 抽象工厂模式
  4. 建造者模式
  5. 原型模式

结构型

  1. 适配器模式
  2. 装饰器模式
  3. 代理模式
  4. 外观模式
  5. 桥接模式
  6. 组合模式
  7. 享元模式

行为型

  1. 策略模式
  2. 模板方法模式
  3. 观察者模式
  4. 迭代子模式
  5. 责任链模式
  6. 命令模式
  7. 备忘录模式
  8. 状态模式
  9. 访问者模式
  10. 中介者模式
  11. 解释器模式

下面重点介绍几种常用的设计模式。

常用设计模式

下面结合设计模式的实际应用,来介绍常用的设计模式,如下图所示。在面试时遇到类似问题,记得要将设计模式与实际业务场景进行结合,来体现对设计模式的理解和应用能力。

          

单例模式

首先是单例模式,这个模式在实际业务中会经常用到,也是设计模式中的主要考察点。这里介绍线程安全的单例模式实现方式。

单例模式常见的实现方式有三种。

  • 第一种是静态初始化方式,也叫作饿汉方式。实现的思路就是在类初始化时完成单例实例的创建,因此不会产生并发问题,在这种方式下不管是否会使用到这个单例,都会创建这个单例。

  • 第二种实现方式是双重检查,也叫作懒汉方式,只有在真正用到这个单例实例的时候才会去创建,如果没有使用就不会创建。这个方式必然会面对多个线程同时使用实例时的并发问题。为了解决并发访问问题,通过 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 实现单例剖析

 

工厂模式

工厂模式是创建不同类型实例时常用的方式,例如 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 方式。

 

扩展