第一次接触设计模式是在2013年维护一个Java电商系统时,当时系统里充斥着大量重复创建数据库连接对象的代码。我的技术主管指着一段代码说:"这里应该用单例模式"。那是我第一次意识到,原来代码组织也有"最佳实践"这种说法。
设计模式本质上是面向对象编程中针对特定场景的解决方案模板。就像建筑领域的标准施工图纸一样,它提供了可复用的代码结构设计方案。经过近十年的实践验证,我认为掌握设计模式有三大实际价值:
在23种经典设计模式中,单例和工厂属于创建型模式,它们都围绕对象实例化过程提供优化方案,但解决的问题维度不同。下面结合我的实际项目经验,详细解析这两种最基础也最常用的模式。
去年优化一个物联网设备管理系统时,日志服务被实例化了上百次,导致内存占用飙升。改用单例后内存消耗直接降低73%。这种性能提升源于单例模式的本质特征:保证一个类仅有一个实例,并提供一个全局访问点。
适合使用单例的典型场景包括:
在Java中实现单例至少有5种写法,但生产环境我最推荐双重检查锁方案:
java复制public class DeviceManager {
private static volatile DeviceManager instance;
private DeviceManager() {}
public static DeviceManager getInstance() {
if (instance == null) {
synchronized (DeviceManager.class) {
if (instance == null) {
instance = new DeviceManager();
}
}
}
return instance;
}
}
这个方案的精妙之处在于:
踩坑提醒:早期Android版本对volatile的实现有缺陷,这种情况下可以用静态内部类方案替代。
在金融项目中见过最离谱的单例滥用是把用户会话信息放在单例中,导致不同用户看到相同数据。切记单例适用于无状态场景,以下情况绝对禁用:
在开发电商促销系统时,我们需要根据活动类型创建不同的优惠策略对象。最初用if-else实现创建逻辑,当策略类型增加到10种时,代码变得难以维护。改用简单工厂后:
java复制public class DiscountFactory {
public static Discount createDiscount(String type) {
switch(type) {
case "FULL_100_MINUS_20":
return new FullDiscount(100, 20);
case "PERCENT_80":
return new PercentDiscount(0.8);
default:
throw new IllegalArgumentException();
}
}
}
简单工厂的核心优势是将对象创建逻辑集中管理,特别适合:
当促销系统需要对接不同电商平台时,我们发现各平台的优惠计算方式存在差异。这时升级为工厂方法模式:
java复制public interface PlatformFactory {
Discount createDiscount();
Coupon createCoupon();
}
public class TmallFactory implements PlatformFactory {
@Override
public Discount createDiscount() {
return new TmallDiscount();
}
}
工厂方法的精髓在于将实例化延迟到子类,符合开闭原则。在以下情况应该考虑升级:
在跨平台UI组件库开发中,我们最终采用了抽象工厂:
java复制public interface GUIFactory {
Button createButton();
Dialog createDialog();
}
public class WindowsFactory implements GUIFactory {
// 实现Windows风格的组件创建
}
抽象工厂适合创建相关或依赖的对象家族,其代价是增加系统复杂度。根据经验,当产品等级超过3层时才值得使用这种模式。
在日志系统设计中,我们通常需要保证日志工厂唯一性:
java复制public class LoggerFactory {
private static final LoggerFactory instance = new LoggerFactory();
private LoggerFactory() {}
public static LoggerFactory getInstance() {
return instance;
}
public Logger createLogger() {
return new FileLogger();
}
}
这种组合模式既保证了工厂实例唯一,又提供了灵活的日志对象创建方式。在Spring框架中,类似的模式被大量使用。
在数据库分库分表场景下,我们需要控制连接池实例数量:
java复制public class ConnectionPool {
private static final Map<String, ConnectionPool> instances = new HashMap<>();
public static ConnectionPool getInstance(String dbName) {
synchronized (instances) {
if (!instances.containsKey(dbName)) {
instances.put(dbName, new ConnectionPool());
}
return instances.get(dbName);
}
}
}
这种扩展版的单例模式,本质上是通过key管理的对象池,在微服务架构中很常见。
经过多年实践,我总结了以下几点经验:
最近在Code Review时发现,有同事在工厂方法中使用了Class.forName()动态加载,这种灵活性的代价是失去了编译期检查。我的建议是:在明确需要扩展性的场景才使用反射,否则应该坚持类型安全。