面试常见设计模式

59 阅读6分钟

一、设计模式须知

  1. 设计模式的作用
    1. 代码复用:例如单例模式、工厂模式
    2. 增强代码拓展性:例如策略模式、观察者模式
    3. 提升代码稳定性和可靠性:例如装饰器模式、迭代器模式
    4. 提升代码规范性,降低复杂度
  2. 单例、工厂、策略、观察者、装饰器、代理、建造者模式是23种设计模式中比较常用于面试的

二、常见设计模式

  1. 单例模式:

    1. 什么是单例模式:一个类有且只有一个实例,提供一个全局访问方法访问该实例

    2. 优缺点:

      1. 优点:
        • 节约系统资源开销
      2. 缺点:
        • 拓展性差,单例类的构造函数都是私有的,只有自己可以实例化
    3. 场景例子:

      1. IOC容器(依赖注入的bean)默认都是单例的
      2. 使用@Bean定义一个自定义线程池,避免线程池资源被滥用
    4. 懒汉式单例模式:

      /**
       * @Description: 懒汉单例模式
       * @Author: zxb
       * @Date: 2025/10/21 12:04
       * @Version: 1.0
       **/
      public class LazySingleton {
          private volatile static LazySingleton instance = null;
          private LazySingleton() {
      
          }
      
          /***
           * 双重校验锁(DCL,Double-check Locking)
           * 双重校验锁是保证多线程环境下单例类的线程安全问题
           * 第一层校验是无锁检查,防止实例已经创建还进入同步代码块加锁,减少锁竞争带来的性能消耗
           * 第二层校验是防止多线程环境下存在多个线程同时进入同步代码块,导致重复创建实例
           * 实例使用volatile的作用是防止指令重排,因为new LazySingleton()创建实例时实际由三条字节命令完成
           * step1:分配内存空间
           * step2:初始化对象
           * step3:将对象的符号引用替换为实际引用(指向分配的内存空间)
           * 由于JVM可能会对不影响最终结果的指令进行重新排序,可能导致step3在step2之前完成,导致其他线程拿到未完全初始化的对象实例
           **/
          public static LazySingleton getInstance() {
              if (instance == null) {
                  synchronized (LazySingleton.class) {
                      if (instance == null) {
                          instance = new LazySingleton();
                      }
                  }
              }
              return instance;
          }
      
      }
      
    5. 饿汉式单例模式:

      /**
       * @Description: 饿汉单例模式
       * @Author: zxb
       * @Date: 2025/10/21 12:08
       * @Version: 1.0
       **/
      public class EagerSingleton {
          private static final EagerSingleton instance = new EagerSingleton();
          private EagerSingleton() {
      
          }
          public static EagerSingleton getInstance() {
              return instance;
          }
      }
      
  2. 工厂模式:

    1. 什么是工厂模式:核心是封装对象的创建逻辑,使调用方无需直接依赖具体类
    2. 优缺点:
      1. 优点:
        • 解耦对象的创建和使用,统一管理对象的生命周期
        • 提高代码的复用性
        • 便于维护和拓展
      2. 缺点:
        • 需要编写额外的工厂类和抽象接口
    3. 场景例子:
      1. Spring上下文ApplicationContext(动态获取Bean、动态读取配置文件)、BeanFactory
  3. 策略模式:

    1. 什么是策略模式:将多个算法封装起来,支持在代码运行过程中根据需要动态切换算法;解决多种相似算法存在时,替换if...else判断,更加简洁,维护性更好

    2. 优缺点:

      1. 优点:

        • 支持在代码运行时动态切换算法,维护性和拓展性更好
      2. 缺点:

        • 策略类数量可能过多,简单场景下增加复杂度
    3. 场景例子:

      1. 通过策略模式实现登录:账号密码登录、短信验证码登录、微信登录、UKey登录,登录方式的选择由请求参数决定

      2. SpringBoot项目可以用@Conditional、@Profile注解实现bean的条件初始化

        // 条件化配置策略
        @Conditional(OnClassCondition.class)      // 类路径条件
        @Conditional(OnBeanCondition.class)       // Bean 存在条件  
        @Conditional(OnPropertyCondition.class)    // 配置属性条件
        @Conditional(OnWebApplicationCondition.class) // Web 应用条件
        
        // 不同环境的不同配置策略
        @Profile("dev")      // 开发环境策略
        @Profile("test")     // 测试环境策略
        @Profile("prod")     // 生产环境策略
        
  4. 观察者模式:

    1. 什么是观察者模式:观察者模式是发布-订阅模式的一种实现,用于定义对象之间一对多的依赖关系,当被观察者状态修改时,所有依赖该对象的观察者都会等到通知
      1. 三要素:被观察者、观察者、自动通知机制
      2. 观察者模式和发布-订阅模式的区别:
        • 观察者模式:被观察者与观察者直接通信
        • 发布-订阅模式:发布和订阅对象之间通过消息代理,间接通信
    2. 优缺点:
      1. 优点:
        • 将被观察者和观察者解耦,被观察者状态修改时自动通知所有观察者
        • 新增观察者无需修改被观察者代码
      2. 缺点:
        • 增加代码复杂度:需要额外维护观察者列表和自动通知逻辑
        • 性能消耗:随着观察者数量的增加,通知耗时增加
    3. 场景例子:
      1. Spring的事件机制就是观察者模式的实践
      2. 不引入mq的情况下,可以通过观察者模式解耦用户注册与消息通知、积分添加等业务
  5. 装饰器模式:

    1. 什么是装饰器模式:通过对象的动态组合替代类继承,实现功能增强

    2. 优缺点:

      1. 优点:
        • 功能灵活组合,避免类膨胀
        • 不修改原有类的代码
        • 能够在程序运行时动态加装饰器
      2. 缺点:
        • 多层装饰增加代码复杂度
        • 无法替代继承的全部场景,例如通用功能还是适合抽取父类,然后子类继承比较好
    3. 场景例子:

      1. Java中的各种IO流就是装饰器模式的经典应用
    4. 为什么装饰器模式可以避免类膨胀?

      例子:基础类咖啡,如果通过继承扩张功能,就会产生拿铁、生椰拿铁、冰拿铁、黄油拿铁等n多类,变成排列组合问题;装饰器模式将加冰、加奶、加糖等操作封装成装饰器,可以动态选择拓展组合,避免类膨胀

  6. 代理模式:

    1. 什么是代理模式:通过引入一个代理对象来控制访问原有对象,代理对象作为中介将访问被代理对象的方法或者属性,同时可以在真正执行前后添加增强逻辑
    2. 优缺点:
      1. 优点:
        • 灵活性:可以在方法执行前后添加增强逻辑
        • 职责分离:代理模式将访问控制和实际业务处理分离
      2. 缺点:
        • 性能消耗:代理对象处理速度会慢一点
    3. 场景例子:
      1. JDK动态代理和Cglib动态代理:前者基于接口实现(通过Proxy创建代理类),后者基于继承实现(通过Enhancer创建子类代理)
      2. AOP面向切面编程
  7. 建造者模式:

    1. 什么是建造者模式:将一个复杂对象构建过程拆分成多个步骤,像积木一样组装得到不同的实例
    2. 优缺点:
      1. 优点:
        • 链式调用,分步构建,灵活可控
        • 适用参数比较多的复杂对象
        • 可以在build方法中对参数进行统一校验
      2. 缺点:
        • 增加代码复杂度
        • 性能比setter、构造方法初始化对象稍微慢一点
      3. 场景例子:
        1. StringBuilder
        2. Lombok提供@Builder注解快速让类支持建造者模式链式调用
        3. 适用对象参数很多的情况