设计模式里最容易在面试和代码里写错的有两类:一类是"怎么把对象只造一个"(单例),一类是"怎么在外面包一层"(代理、适配器、装饰器)。后者尤其容易混——三者都包一层对象,但意图完全不同。

本文先讲单例的两种写法(恶汉式与懒汉式 / Double Check),再讲代理的静态与动态两种形态,最后把代理、适配器、装饰器摆在一起辨析,并给一张"易混模式辨析表"。

重点提醒:代理、适配器、装饰器可以同时使用,但每个类只该承担一种意图。


一、单例模式:恶汉式与懒汉式(含 Double Check)

单例模式:分为恶汉式和懒汉式。

恶汉式(类加载即创建实例):

public class Singleton
{
    private static Singleton instance = new Singleton();
    public static Singleton getInstance()
    {
        return instance ;
    }
}

懒汉式(延迟创建,Double Check 写法):

public class Singleton02
{
    private static Singleton02 instance;
    public static Singleton02 getInstance()
    {
        if (instance == null)
        {
            synchronized (Singleton02.class)
            {
                if (instance == null)
                {
                    instance = new Singleton02();
                }
            }
        }
        return instance;
    }
}

恶汉式在类加载时就创建实例,简单但可能提前占用资源;懒汉式把创建推迟到第一次调用,并用双重检查(Double Check)+ synchronized 保证并发下只创建一个,是面试里常被要求现场写出的写法。

两个容易踩的坑

  • 懒汉式的 Double Check 必须两层判空。外层判空避免每次都进锁,内层 synchronized 里还要再判断一次,否则并发下可能重建实例。
  • 恶汉式无法延迟加载。实例在类加载即创建,若初始化很重且未必用到会浪费资源。

什么时候用:需要全局唯一实例时(如全局配置、连接池)。对启动性能敏感、实例未必用到的场景用懒汉式;对简单、必定用到的场景用恶汉式更直接。


二、代理模式:静态代理与动态代理

代理模式:在某些情况下,一个用户不想或不能直接引用一个对象,此时可以通过一个称之为"代理"的第三者来实现间接引用,代理对象可以在客户端和目标对象之间起中介的作用,并且可以通过中介对象去掉用户不能看到的内容和服务,或者添加用户需要的额外服务。

静态代理:即在程序运行前代理类就已经存在,也就是编写代码的时候已经将代理类的代码写好。

动态代理:在程序运行时,通过反射机制动态创建代理类。常见的有 JDK 代理与 CGLib 代理两种实现路线。

两个容易踩的坑

  • 静态代理类需预先写好。代理类在程序运行前就已存在,被代理类变多时代理类会随之膨胀。
  • 动态代理在运行时通过反射创建。JDK 代理与 CGLib 代理是常见实现路线;动态代理灵活、适合通用增强,但比静态代理多一层运行时创建过程。

什么时候用:需要在客户端和目标对象之间加中介逻辑(如权限、日志、延迟加载)时用代理。被代理类数量少、逻辑固定时写静态代理更直接;需要通用、可批量增强时用动态代理。


三、代理 vs 适配器 vs 装饰器:易混辨析

代理、适配器和装饰器都包一层对象,但意图不同。代理控制访问:懒加载、权限、远程调用,接口通常与真实对象相同。适配器让不兼容的接口能被调用方使用,它改变的是形状而不是职责。装饰器在不改类的前提下叠加行为,可以多层嵌套。面试里容易把 Spring AOP、InputStream 包装和老系统对接混为一谈。判断标准是:你是在限制访问、在翻译接口,还是在叠加功能。

三个容易踩的坑

  • 代理:调用方以为自己在用原对象,实际被拦截。远程代理、虚拟代理、保护代理都属此类。
  • 适配器:两边接口本来对不上。写成 Adapter 是为了复用已有类,而不是为了加日志。
  • 装饰器:接口不变,一层层加缓存、压缩、加密。Java IO 流是典型;不要用继承爆炸替代装饰。

什么时候用:需要权限或延迟创建时用代理;对接第三方 SDK 用适配器;要给对象加可组合的增强用装饰器。三者可以同时出现,但每个类只承担一种意图。重构时先写清"调用方期望的接口"再选模式,不要先套名字。


附:易混模式辨析表

模式 意图 接口是否相同 典型场景 典型例子
单例 保证全局唯一实例 全局配置、连接池 恶汉式、懒汉式(Double Check)
静态代理 运行前写好代理类,做中介 同被代理对象 少量被代理类、逻辑固定 手写代理类
动态代理 运行时反射创建代理类 同被代理对象 大量 / 通用增强 JDK 代理、CGLib 代理
代理 控制访问(懒加载 / 权限 / 远程) 通常与真实对象相同 权限、延迟创建、远程调用 Spring AOP、远程代理、保护代理
适配器 翻译不兼容的接口 改变形状,职责不变 对接老系统 / 第三方 SDK 老系统对接
装饰器 不改类前提下叠加行为 相同,可多层嵌套 可组合的增强 Java IO 流(缓存 / 压缩 / 加密)