1. 为什么设计模式是Java开发者的必修课
第一次接触设计模式是在2013年,当时接手一个遗留系统时被各种混乱的代码折磨得够呛。直到在代码深处发现几处运用了工厂模式的清晰模块,才让我意识到设计模式的价值。设计模式不是炫技的工具,而是解决特定场景下代码组织问题的经验总结。
在Java生态中,单例、工厂和代理这三种模式几乎出现在每个成熟框架里。Spring的Bean默认单例、MyBatis的SqlSessionFactory、动态代理实现的AOP,这些都是模式的最佳实践。理解它们,就等于拿到了阅读框架源码的钥匙。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 单例模式:从饿汉式到枚举实现
2.1 基础实现与线程安全陷阱
最常见的饿汉式写法看似简单却暗藏玄机:
java复制public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
这种写法在类加载时就初始化实例,虽然线程安全但可能造成资源浪费。我在电商项目中就遇到过——某个单例类加载了大量缓存数据,但实际上整个服务周期都没被调用过。
懒汉式的双重检查锁定(DCL)更符合按需创建的原则:
java复制public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
注意这里的volatile关键字必不可少。我曾排查过一个线上问题:某服务在高压下出现空指针异常,就是因为缺少volatile导致指令重排序,其他线程拿到了未初始化完成的对象。
2.2 枚举单例:Joshua Bloch的终极方案
《Effective Java》推荐的枚举实现方式:
java复制public enum Singleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
这种方式不仅能防止反射攻击,还能自动处理序列化问题。在分布式配置中心项目中,我们用它来管理全局配置,相比传统实现减少了30%的防御性代码。
2.3 场景单例与全局单例的差异
游戏开发中常提到的"场景单例",本质是通过场景栈管理的生命周期:
java复制// 伪代码示例
public class SceneSingleton {
private static Map<Scene, SceneSingleton> instances = new WeakHashMap<>();
public static SceneSingleton getInstance(Scene scene) {
return instances.computeIfAbsent(scene, s -> new SceneSingleton());
}
public static void clearScene(Scene scene) {
instances.remove(scene);
}
}
与全局单例不同,场景单例会随场景销毁而释放资源。在Unity项目里,我们用它来管理场景特定的音频资源,避免了内存泄漏问题。
3. 工厂模式:从简单工厂到抽象工厂
3.1 简单工厂的实用场景
支付网关集成时的典型应用:
java复制public class PaymentFactory {
public static Payment create(String type) {
switch (type) {
case "ALIPAY": return new AlipayAdapter();
case "WECHAT": return new WechatPayAdapter();
default: throw new IllegalArgumentException();
}
}
}
虽然违背开闭原则,但在支付渠道固定的内部系统中,这种写法反而更直观。我们在ERP系统中用这种模式集成过7种支付方式,后续扩展时通过策略模式进行了改造。
3.2 工厂方法模式的框架级应用
Spring中的BeanFactory就是典型工厂方法:
java复制public interface BeanFactory {
Object getBean(String name);
<T> T getBean(Class<T> requiredType);
}
我在开发规则引擎时借鉴了这种设计:
java复制public interface RuleFactory {
Rule createRule(RuleConfig config);
}
public class DiscountRuleFactory implements RuleFactory {
@Override
public Rule createRule(RuleConfig config) {
return new DiscountRule(config.getThreshold());
}
}
这种设计让新增规则类型时只需扩展新的工厂,不影响现有逻辑。
3.3 抽象工厂在跨平台UI中的应用
GUI库的经典案例:
java复制public interface GUIFactory {
Button createButton();
Dialog createDialog();
}
public class WinFactory implements GUIFactory {
public Button createButton() { return new WinButton(); }
public Dialog createDialog() { return new WinDialog(); }
}
public class MacFactory implements GUIFactory {
public Button createButton() { return new MacButton(); }
public Dialog createDialog() { return new MacDialog(); }
}
在Android/iOS跨平台项目中,我们用类似结构实现了90%的UI代码复用。关键在于将工厂实例的创建时机提前到应用启动时:
java复制public class AppConfig {
private static GUIFactory factory;
public static void init(Platform platform) {
if (platform == Platform.ANDROID) {
factory = new AndroidFactory();
} else {
factory = new IOSFactory();
}
}
public static GUIFactory getFactory() {
return factory;
}
}
4. 代理模式:从静态代理到Spring AOP
4.1 静态代理的调试价值
虽然笨重,但在调试时很有用:
java复制public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void save(User user) {
long start = System.currentTimeMillis();
target.save(user);
System.out.println("耗时:" + (System.currentTimeMillis() - start));
}
}
在排查一个ORM性能问题时,我用这种代理快速定位到了N+1查询的代码位置。虽然最终被动态代理取代,但作为调试工具很有价值。
4.2 JDK动态代理的局限性
接口代理的标准写法:
java复制public class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法:" + method.getName());
return method.invoke(target, args);
}
}
UserService proxy = (UserService) Proxy.newProxyInstance(
loader,
new Class[]{UserService.class},
new LogInvocationHandler(target)
);
但遇到非接口类时就无能为力了。在监控系统开发中,我们不得不为某些第三方类手动创建接口适配层。
4.3 CGLIB字节码增强实战
Spring AOP的默认实现方式:
java复制public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getProxy(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("前置处理");
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置处理");
return result;
}
}
在数据权限控制系统中,我们用CGLIB实现了基于注解的自动过滤:
java复制@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataAuth {
String value();
}
// 在intercept方法中处理注解
DataAuth auth = method.getAnnotation(DataAuth.class);
if (auth != null) {
String deptId = SecurityContext.getCurrentDept();
args[0] = args[0] + " AND dept_id = '" + deptId + "'";
}
5. 模式混用的实战案例
5.1 数据库连接池中的模式组合
以HikariCP为例:
- 单例模式:整个应用通常只需要一个连接池实例
- 工厂模式:通过DataSource接口隐藏具体实现
- 代理模式:对Connection对象进行代理实现归还逻辑
java复制public class ProxyConnection implements Connection {
private Connection realConnection;
private HikariPool pool;
@Override
public void close() throws SQLException {
pool.releaseConnection(this); // 代理真实关闭行为
}
// 其他方法委托给realConnection
}
这种设计让使用者无需关心连接生命周期管理。在分库分表中间件开发中,我们借鉴这种思路实现了连接路由。
5.2 缓存系统的分层设计
多级缓存架构中的模式应用:
java复制public class CacheManager {
private static volatile CacheManager instance; // 单例
private Map<String, CacheFactory> factories; // 抽象工厂
public Cache getCache(String type) {
CacheFactory factory = factories.get(type);
if (factory == null) {
factory = new DefaultCacheFactory(); // 工厂方法
factories.put(type, factory);
}
return factory.createCache();
}
public Cache getProxyCache(Cache target) { // 代理模式
return (Cache) Proxy.newProxyInstance(...);
}
}
在实际项目中,这种结构让本地缓存与Redis的无缝切换成为可能。通过代理模式,我们还实现了缓存穿透保护逻辑。
6. 那些年我踩过的模式坑
6.1 单例的内存泄漏问题
在Android开发中,持有了Activity引用的单例会导致整个Activity无法回收。解决方案:
java复制public class SafeSingleton {
private static volatile SafeSingleton instance;
private WeakReference<Context> contextRef;
private SafeSingleton(Context context) {
this.contextRef = new WeakReference<>(context.getApplicationContext());
}
}
6.2 过度设计的工厂陷阱
曾经为一个只有三种实现的DAO层设计了完整的抽象工厂体系,结果只是增加了维护成本。经验法则是:当变化维度确实存在时再引入工厂模式。
6.3 动态代理的性能开销
在高频调用的支付验证逻辑中使用动态代理,导致性能下降40%。最终解决方案:
java复制// 改用静态代理+编译时织入
@Aspect
public class PaymentAspect {
@Around("execution(* com..PaymentService.*(..))")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
// 增强逻辑
}
}
7. 设计模式的演进思考
随着函数式编程的兴起,有些模式有了新实现。比如用Supplier实现懒加载单例:
java复制public class LazySingleton {
private static final Supplier<LazySingleton> INSTANCE = () -> {
LazySingleton instance = new LazySingleton();
INSTANCE = () -> instance; // 替换为直接返回
return instance;
};
public static LazySingleton getInstance() {
return INSTANCE.get();
}
}
在Java8+项目中,这种写法比双重检查锁定更简洁。但核心思想没变——控制实例化过程,保证全局唯一性。
