设计模式里最容易在面试和代码里写错的有两类:一类是"怎么把对象只造一个"(单例),一类是"怎么在外面包一层"(代理、适配器、装饰器)。后者尤其容易混——三者都包一层对象,但意图完全不同。
本文先讲单例的两种写法(恶汉式与懒汉式 / 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 流(缓存 / 压缩 / 加密) |
Java 设计模式实现:单例、代理与易混模式辨析
https://lautung.com/archives/java-design-patterns
评论