概述
在软件设计的宏大叙事中,“对象的创建”从来不是一行简单的 new 语句。当系统变得复杂、需求开始多变,对象创建的时机、方式、数量与结构便成为影响系统可维护性、扩展性与性能的关键杠杆。创建型设计模式的共同使命正在于此——将对象的创建与使用分离,让客户端代码无需关心“具体由谁、在何时、以何种方式”构造对象,从而从容应对“变化”这一软件开发中唯一的永恒。
创建型模式家族共有五位核心成员,它们各自把守一道变化之门:单例模式控制实例数量,确保全局唯一;工厂方法模式将实例化延迟到子类,顺应类型体系的扩展;抽象工厂模式管理产品族,保障一族内对象的兼容共生;建造者模式理清复杂对象的构建步骤,让相同的构造过程可以产生不同表示;原型模式则另辟蹊径,以拷贝代替新建,在运行时动态复制对象状态。
本文将构建一个立体的创建型知识体系闭环:从一张综合对比矩阵出发,快速建立模式间的差异认知;通过十组易混淆模式的深度辨析,刺破概念迷雾;沿着演进路线图,理解模式诞生的逻辑必然;深入Spring框架源码,观察模式在工业级产品中的协同舞蹈;最后,通过决策指南、分布式实践、反模式警示与专家级面试题,将理论拉回工程现场。这是一次创建型模式的收官之旅,让我们一同抵达对象创建的艺术腹地。
一、五种创建型模式核心对比
1.1 总览对比表
| 对比维度 | 单例模式 (Singleton) | 工厂方法模式 (Factory Method) | 抽象工厂模式 (Abstract Factory) | 建造者模式 (Builder) | 原型模式 (Prototype) |
|---|---|---|---|---|---|
| 核心意图 | 确保类只有一个实例,并提供全局访问点 | 定义一个用于创建对象的接口,让子类决定实例化哪一个类 | 提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类 | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 | 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象 |
| 解决的核心痛点 | 全局状态一致性、资源集中管理、避免重复初始化开销 | 客户端代码与具体产品类紧耦合,产品类型可能动态变化 | 产品族切换困难,客户端需了解太多具体类,产品间兼容性难保证 | 构造参数过多、参数组合复杂、需要构建不可变对象、步骤逻辑复杂 | 创建新对象成本高昂(如数据库连接),或需保留对象某一时刻的快照 |
| 变化维度 | 实例数量(有且仅有1个) | 产品类型(单一产品等级结构扩展) | 产品族(多个产品等级结构的组合扩展) | 构建步骤顺序、内部表示 | 运行时对象状态 |
| 关键角色数量 | 1个角色(单例类自身) | 4个角色(抽象产品、具体产品、抽象工厂、具体工厂) | 5个角色(抽象产品A/B、具体产品A/B、抽象工厂、具体工厂) | 4个角色(产品、抽象建造者、具体建造者、指导者) | 3个角色(原型接口、具体原型、客户端) |
| JDK典型应用 | java.lang.Runtime、java.awt.Desktop | java.util.Calendar.getInstance() | javax.xml.parsers.DocumentBuilderFactory | java.lang.StringBuilder、java.nio.ByteBuffer | java.lang.Object.clone()、java.lang.Cloneable |
| Spring体现 | Bean默认作用域singleton、@Configuration单例保障 | FactoryBean接口、BeanFactory层次结构 | BeanFactory与ListableBeanFactory家族、ApplicationContext | BeanDefinitionBuilder、GenericApplicationContext的注册流程 | @Scope("prototype")、AbstractBeanDefinition的prototype作用域 |
1.2 模式本质差异解读
上述表格勾勒出五个模式的轮廓,但理解其本质差异需要更进一步。
单例模式与其他模式的根本分歧在于“数量约束”。它不是关于如何创建,而是关于创建多少。当其他模式聚焦于创建过程的灵活性与可扩展性时,单例却反其道而行之——通过约束来换取全局状态的统一。值得注意的是,单例模式与“单一职责原则”时常发生冲突:单例类往往既负责自身的业务逻辑,又负责自身的实例管理。
工厂方法与抽象工厂的区分核心在于“维度”。工厂方法应对的是“产品等级结构”这一维度的变化(例如,不同的日志记录器:FileLogger、ConsoleLogger)。而抽象工厂应对的是“产品族”这一维度的变化(例如,Windows风格的窗口+按钮 与 Mac风格的窗口+按钮)。抽象工厂本质上是一组工厂方法的集合,它们共同服务于同一个产品族主题。
建造者模式的独特之处在于“步骤的秩序”。它关注的不是创建什么产品,而是如何分步骤、有秩序地装配出一个复杂对象。当构造器参数膨胀到难以阅读,或者某些参数之间存在依赖关系(例如必须先设置A才能设置B),建造者模式便显现出其价值。它与抽象工厂的差异是明显的:抽象工厂是一次性返回完整产品,建造者则是“步步为营”。
原型模式的视角最为特殊——它从“性能”与“状态拷贝”切入。在某些场景下,创建一个新对象并初始化到特定状态的开销,远大于复制一个已有对象。原型模式通过克隆绕过了构造器的束缚,但同时也将“深克隆 vs 浅克隆”的难题摆上了台面。
二、两两对比辨析矩阵
理解模式边缘的最佳方式是两两对撞。下面我们将易混淆的组合置于同一透镜下审视。
2.1 单例模式 vs 静态工具类
两者都能提供全局访问的方法,但本质截然不同。单例是实例,可以继承接口、延迟初始化、管理状态;静态工具类则是一堆静态方法的容器,不具备面向对象的动态特性。
// 静态工具类:无法实现多态,难以被Mock替换
public class MathUtils {
private MathUtils() {}
public static int add(int a, int b) { return a + b; }
}
// 单例模式:可以实现接口,能够进行延迟加载和状态管理
public class ConfigurationManager {
private static volatile ConfigurationManager instance;
private Properties props;
private ConfigurationManager() {
// 可以执行复杂的初始化逻辑
props = new Properties();
// 从文件/网络加载配置...
}
public static ConfigurationManager getInstance() {
if (instance == null) {
synchronized (ConfigurationManager.class) {
if (instance == null) {
instance = new ConfigurationManager();
}
}
}
return instance;
}
public String getProperty(String key) { return props.getProperty(key); }
}
关键差异:单例是对象的单例化,静态类是方法集合的静态化。单例可以被注入、被代理、被延迟加载,静态类则不具备这些动态特性。
2.2 简单工厂 vs 工厂方法 vs 抽象工厂
这是一个经典的演进三部曲。
// 1. 简单工厂:违反开闭原则,新增产品需修改工厂类
class SimplePizzaFactory {
public static Pizza createPizza(String type) {
if ("cheese".equals(type)) return new CheesePizza();
else if ("veggie".equals(type)) return new VeggiePizza();
else throw new IllegalArgumentException();
}
}
// 2. 工厂方法:符合开闭原则,新增产品只需新增具体工厂
interface PizzaFactory {
Pizza createPizza();
}
class CheesePizzaFactory implements PizzaFactory {
public Pizza createPizza() { return new CheesePizza(); }
}
class VeggiePizzaFactory implements PizzaFactory {
public Pizza createPizza() { return new VeggiePizza(); }
}
// 3. 抽象工厂:管理产品族
interface PizzaIngredientFactory {
Dough createDough();
Sauce createSauce();
}
class NYIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() { return new ThinCrustDough(); }
public Sauce createSauce() { return new MarinaraSauce(); }
}
关键差异:简单工厂是“一个类根据参数创建所有产品”;工厂方法是“一个抽象方法对应一个产品”;抽象工厂是“一组工厂方法对应一个产品族”。
2.3 工厂方法 vs 模板方法模式
工厂方法是模板方法模式在“对象创建”领域的特化应用。
// 模板方法模式:定义算法骨架
abstract class DataProcessor {
public final void process() {
Object data = readData(); // 子类实现
Object result = handle(data); // 子类实现
writeResult(result); // 子类实现
}
protected abstract Object readData();
protected abstract Object handle(Object data);
protected abstract void writeResult(Object result);
}
// 工厂方法模式:骨架专用于创建对象
abstract class Application {
// 模板方法
public void run() {
Document doc = createDocument(); // 工厂方法
doc.open();
doc.close();
}
protected abstract Document createDocument();
}
关键差异:工厂方法是模板方法模式的一种特化,其“变化的部分”仅限于对象的创建。
2.4 抽象工厂 vs 建造者模式
抽象工厂负责创建完整的产品族,建造者负责分步骤构建一个复杂产品。
// 抽象工厂:一次性返回完整对象
interface WidgetFactory {
Window createWindow();
Button createButton();
}
// 使用
WidgetFactory factory = new MacWidgetFactory();
Window window = factory.createWindow(); // 完整对象
// 建造者:分步骤构建
class ComputerBuilder {
private Computer computer = new Computer();
public ComputerBuilder cpu(String cpu) { computer.setCpu(cpu); return this; }
public ComputerBuilder ram(String ram) { computer.setRam(ram); return this; }
public ComputerBuilder disk(String disk) { computer.setDisk(disk); return this; }
public Computer build() { return computer; }
}
// 使用
Computer pc = new ComputerBuilder().cpu("i7").ram("16G").disk("1T").build();
2.5 建造者模式 vs 链式setter
链式setter只是语法糖,缺乏建造者模式的核心能力:构建过程控制与不变性保证。
// 链式setter:对象创建后可被修改
class Student {
private String name;
public Student setName(String name) { this.name = name; return this; }
// 缺少构建完成后的校验和不可变保证
}
// 建造者模式:构建完成后对象不可变,可在build()中执行校验
class Student {
private final String name;
private final int age;
private Student(Builder builder) {
this.name = builder.name;
this.age = builder.age;
}
public static class Builder {
private String name;
private int age;
public Builder name(String name) { this.name = name; return this; }
public Builder age(int age) { this.age = age; return this; }
public Student build() {
if (name == null || age <= 0) {
throw new IllegalStateException("Invalid student");
}
return new Student(this);
}
}
}
2.6 原型模式 vs 工厂方法模式
原型模式以实例为模板,工厂方法以类为模板。
// 工厂方法:基于类创建
interface LoggerFactory { Logger createLogger(); }
// 原型模式:基于实例克隆
class ReportTemplate implements Cloneable {
private String header;
private String footer;
private List<String> defaultRows;
@Override
public ReportTemplate clone() {
// 深克隆实现
}
}
2.7 原型模式 vs 单例模式(数量对立但可组合)
一个追求“多”,一个追求“一”,但两者可以组合使用:原型管理器本身通常是一个单例。
// 原型管理器(单例)管理多个原型
public enum PrototypeManager {
INSTANCE;
private Map<String, Cloneable> prototypes = new HashMap<>();
public void register(String key, Cloneable prototype) {
prototypes.put(key, prototype);
}
public Cloneable create(String key) {
return prototypes.get(key).clone(); // 通过克隆创建新实例
}
}
2.8 工厂方法 vs 建造者模式(创建单对象时的选择)
创建单个对象时,若对象简单用工厂方法,若对象复杂用建造者。
// 工厂方法:对象简单,构造参数少
interface ConnectionFactory { Connection createConnection(); }
// 建造者:对象复杂,参数众多且有依赖关系
HttpRequest request = new HttpRequest.Builder()
.url("https://api.example.com")
.method("POST")
.header("Content-Type", "application/json")
.body(jsonBody)
.timeout(5000)
.retry(3)
.build();
2.9 抽象工厂 vs 原型模式(产品族切换方案对比)
抽象工厂通过类组合切换产品族,原型模式通过实例替换切换。
// 抽象工厂方式
UIFactory factory = new WindowsUIFactory(); // 切换为Windows风格
Button btn = factory.createButton();
// 原型方式:预先注册不同风格的原型实例
PrototypeManager.register("win.btn", new WindowsButton());
PrototypeManager.register("mac.btn", new MacButton());
Button btn = PrototypeManager.create("win.btn").clone();
原型方式更加动态,可以在运行时增加或替换原型,而抽象工厂需要编写新的工厂类。
2.10 单例模式 vs 原型模式(对象复用方式的差异)
单例复用同一个实例,原型复用同一份初始状态。
// 单例:全局唯一实例,状态共享
ConfigManager.getInstance().set("key", "value");
// 原型:每次克隆得到独立实例,初始状态相同但后续独立
Report template = new Report("标准模板");
Report report1 = template.clone(); // 独立副本
Report report2 = template.clone(); // 另一个独立副本
2.11 对比关系图
flowchart TD
subgraph 创建型模式核心差异轴线
A[变化维度] --> B[数量]
A --> C[类型]
A --> D[产品族]
A --> E[步骤]
A --> F[状态]
B --> G[单例模式<br/>有且仅有一个实例]
C --> H[工厂方法<br/>延迟到子类决定类型]
D --> I[抽象工厂<br/>创建一系列相关对象]
E --> J[建造者模式<br/>分步骤构建复杂对象]
F --> K[原型模式<br/>克隆已有实例]
end
G -.->|数量对立但可组合| K
H -.->|特化为对象创建| E
I -.->|一组工厂方法| H
J -.->|最终返回完整产品| H
图表解读:上图以“变化维度”为核心轴线,将五种创建型模式锚定在各自擅长的变化应对领域。单例模式与原型模式在“数量”轴上构成两极:一个严格控制为1,一个鼓励通过克隆生成任意多个。工厂方法与建造者模式在“创建单个对象”的维度上形成互补:前者关注“由谁创建”,后者关注“如何创建”。抽象工厂本质上是工厂方法的聚合体,它解决了更高层次的“产品族协同”问题。理解这五条轴线的差异,是掌握创建型模式选型的关键。
三、创建型模式的演进路线图
设计模式并非凭空诞生,而是对既有代码坏味道的逐步改良。创建型模式之间存在一条清晰的演进脉络。
graph LR
A["硬编码 new"] -->|"推动力:封装变化点"| B["简单工厂"]
B -->|"推动力:遵循开闭原则"| C["工厂方法模式"]
C -->|"推动力:应对产品族扩展"| D["抽象工厂模式"]
A -->|"推动力:分离构造逻辑"| E["建造者模式"]
A -->|"推动力:以拷贝替代新建"| F["原型模式"]
subgraph "演进驱动力"
direction TB
G["单一职责原则"]
H["开闭原则"]
I["接口隔离原则"]
J["依赖倒置原则"]
end
B -.->|"体现"| G
C -.->|"体现"| H
D -.->|"体现"| I
E -.->|"体现"| G
F -.->|"体现"| H
3.1 从硬编码new到简单工厂
最初的代码中,对象创建直接使用 new ConcreteClass()。当具体类的名称发生变化,或需要根据条件创建不同子类时,所有 new 出现的地方都需要修改,违反了单一职责原则——客户端既负责业务逻辑,又负责对象创建。
推动力:封装创建点,初步解耦。简单工厂将所有 new 语句集中到一个工厂类中,客户端只需告知工厂“我要什么”,而无需知道“怎么造”。
// 演进前
if (type == "A") { obj = new ConcreteA(); }
else { obj = new ConcreteB(); }
// 演进后
Product obj = SimpleFactory.create(type);
3.2 从简单工厂到工厂方法
简单工厂虽然集中了创建逻辑,但当新增产品类型时,仍需修改工厂类的 create 方法(添加新的 if-else 分支),这违背了开闭原则——对扩展开放,对修改关闭。
推动力:遵循开闭原则。工厂方法模式将工厂也抽象化,每个具体产品对应一个具体工厂。新增产品时,只需新增对应的工厂类,无需修改已有代码。
3.3 从工厂方法到抽象工厂
当系统需要处理多个相关的产品等级结构(例如UI组件库:窗口、按钮、文本框),并且这些产品必须成套使用时,工厂方法就显得力不从心——客户端需要组合多个工厂。
推动力:应对产品族扩展需求,保证产品间兼容性。抽象工厂将一组相关的工厂方法聚合到一个接口中,确保一个产品族内的所有对象能够协同工作。
3.4 从复杂构造器到建造者模式
当一个类的构造函数参数超过4个,尤其是存在多个可选参数、参数之间存在依赖关系时,重叠构造器模式或JavaBeans模式都会带来问题:前者导致代码难以阅读,后者破坏了对象的不可变性。
推动力:分离复杂对象的构建与表示,解决参数爆炸与不可变对象构建问题。建造者模式通过链式调用,让构造过程清晰、有序、自解释。
3.5 从重复初始化到原型模式
某些对象的创建成本极高(如包含大量数据库查询、网络请求的初始化过程)。如果每次需要类似对象时都重新执行完整的构造流程,将导致严重的性能问题。
推动力:以拷贝替代新建,优化性能,支持动态状态保存。原型模式允许保存一个“快照”作为模板,后续通过克隆快速生成相似实例。
四、框架源码中的创建型模式协同应用
Spring Framework是创建型模式应用的典范之作,它巧妙地将多种模式编织进IoC容器的设计之中。
4.1 Spring IoC容器:创建型模式的集大成者
Spring的 BeanFactory 体系是工厂方法模式与抽象工厂模式的完美融合:
- 工厂方法模式:
BeanFactory.getBean(String)是一个典型的工厂方法,由具体容器实现(如DefaultListableBeanFactory)决定如何创建Bean。 - 抽象工厂模式:
ListableBeanFactory扩展了BeanFactory,提供了创建“一系列”Bean的能力(如getBeansOfType),它管理着整个产品族(应用程序中所有的Bean)。 - 单例模式:Spring通过三级缓存(
singletonObjects、earlySingletonObjects、singletonFactories)管理单例Bean,确保在容器范围内一个ID对应唯一实例。
4.2 Bean创建流程中的模式协作
sequenceDiagram
participant Client
participant AbstractBeanFactory as AbstractBeanFactory(抽象工厂)
participant AbstractAutowireCapableBeanFactory as AbstractAutowireCapableBeanFactory(工厂方法+建造者)
participant BeanWrapper as BeanWrapperImpl(建造者)
participant SingletonRegistry as DefaultSingletonBeanRegistry(单例注册表)
Client->>AbstractBeanFactory: getBean(name)
AbstractBeanFactory->>AbstractBeanFactory: doGetBean()
AbstractBeanFactory->>SingletonRegistry: getSingleton(name)
alt 单例已存在
SingletonRegistry-->>AbstractBeanFactory: 返回缓存的单例
else 未创建
AbstractBeanFactory->>AbstractAutowireCapableBeanFactory: createBean() [工厂方法]
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory: doCreateBean()
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory: createBeanInstance() [策略模式选择实例化方式]
AbstractAutowireCapableBeanFactory->>SingletonRegistry: addSingletonFactory() [提前暴露引用]
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory: populateBean() [建造者式属性填充]
AbstractAutowireCapableBeanFactory->>BeanWrapper: setPropertyValues()
BeanWrapper-->>AbstractAutowireCapableBeanFactory: 属性设置完成
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory: initializeBean()
AbstractAutowireCapableBeanFactory->>SingletonRegistry: addSingleton() [放入单例池]
end
AbstractBeanFactory-->>Client: Bean实例
图表解读:该时序图揭示了Spring Bean创建过程中多个创建型模式的交响。AbstractBeanFactory 的 getBean 方法是工厂方法模式的核心入口。doCreateBean 内部,首先通过 createBeanInstance 实例化对象(支持单例或原型作用域的策略选择),然后通过 populateBean 执行属性填充——这个过程类似于建造者模式的分步骤装配,BeanWrapper 充当了建造者的角色。DefaultSingletonBeanRegistry 的三级缓存机制保障了单例的全局唯一性与循环依赖的解决。整个流程将对象创建、属性赋值、初始化分离,是单一职责与开闭原则的经典实践。
4.3 FactoryBean接口:工厂方法与建造者的结合
FactoryBean 接口允许开发者自定义复杂Bean的创建逻辑,是工厂方法模式的直接体现。以MyBatis整合Spring的 SqlSessionFactoryBean 为例:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean {
private Resource configLocation;
private DataSource dataSource;
// 建造者风格的配置方法
public void setConfigLocation(Resource configLocation) { ... }
public void setDataSource(DataSource dataSource) { ... }
@Override
public SqlSessionFactory getObject() throws Exception {
// 工厂方法:根据配置创建复杂的SqlSessionFactory
return new SqlSessionFactoryBuilder().build(...);
}
}
这里,SqlSessionFactoryBean 本身使用建造者风格的setter方法收集配置参数,而 getObject() 则是工厂方法的实现,内部可能再次委托给MyBatis自身的 SqlSessionFactoryBuilder 建造者。三层模式嵌套,实现了配置灵活性与对象构建复杂性的隔离。
4.4 @Configuration的CGLIB代理与单例保障
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService(myRepository()); // 直接调用myRepository()方法
}
@Bean
public MyRepository myRepository() {
return new MyRepository();
}
}
Spring通过CGLIB动态代理为 @Configuration 类生成子类。当 myService() 方法内部调用 myRepository() 时,实际调用的是代理对象的 myRepository() 方法,该方法会首先检查IoC容器中是否已存在该Bean的单例,若存在则直接返回,从而保障了 @Bean 方法的单例语义。这一机制的本质是:用继承(CGLIB子类)重写工厂方法,在工厂方法内部植入单例逻辑。
五、创建型模式的选型决策指南
5.1 决策流程图
flowchart TD
Start(["开始:需要创建对象"]) --> Q1{"对象创建成本是否极高?"}
Q1 -->|"是"| Q2{"是否需要动态保存/恢复状态?"}
Q2 -->|"是"| Prototype["原型模式"]
Q2 -->|"否"| Singleton["单例模式 或 对象池"]
Q1 -->|"否"| Q3{"构造函数参数是否超过4个?"}
Q3 -->|"是"| Q4{"参数之间是否有依赖/顺序?"}
Q4 -->|"是"| Builder["建造者模式"]
Q4 -->|"否"| Q5{"是否需要构建不可变对象?"}
Q5 -->|"是"| Builder
Q3 -->|"否"| Q6{"产品类型是否会频繁扩展?"}
Q6 -->|"是"| Q7{"是否有多个产品族需要协同?"}
Q7 -->|"是"| AbstractFactory["抽象工厂模式"]
Q7 -->|"否"| FactoryMethod["工厂方法模式"]
Q6 -->|"否"| Q8{"是否需要全局唯一实例?"}
Q8 -->|"是"| Singleton
Q8 -->|"否"| Simple["简单工厂 或 直接new"]
图表解读:决策树从最核心的问题特征出发,引导开发者走向合适的设计模式。创建成本高是原型模式与单例模式的共同切入点,两者的区别在于是否允许多个不同状态的对象存在。参数多是建造者模式最直接的信号。类型扩展频繁则将决策引向工厂家族:单一产品线用工厂方法,多产品族用抽象工厂。这一决策流程并非绝对,实际工程中往往需要综合考量多种因素,但它提供了一个清晰的思考起点。
5.2 技术选型量化参考指标
| 模式 | 量化参考指标 | 典型场景 |
|---|---|---|
| 单例模式 | 实例化时间 > 100ms;全局状态必须一致;JVM内该对象只有一个 | 配置管理器、连接池、线程池 |
| 工厂方法模式 | 产品子类数量 ≥ 3 且仍在增长;客户端不应感知具体类名 | 日志框架适配(Log4j/Logback)、DAO工厂 |
| 抽象工厂模式 | 产品族数量 ≥ 2;产品间有强兼容性要求;客户端需动态切换产品族 | 跨平台UI库、多数据库方言适配 |
| 建造者模式 | 构造参数数量 > 4;可选参数占比 > 30%;需要构建不可变对象 | HTTP请求构造、复杂配置对象、SQL构建器 |
| 原型模式 | 克隆成本 < 1/10 新建成本;需要保留对象历史快照 | 图形编辑器复制粘贴、数据库记录缓存克隆 |
六、分布式场景下的创建型模式综合应用
微服务与分布式架构为创建型模式带来了新的挑战与机遇。
6.1 分布式单例的实现与局限
在分布式环境中,单例的“唯一性”语义从JVM级别扩展到了集群级别。
- Redis分布式锁:通过
SETNX+ 过期时间实现,轻量但存在锁续期问题。 - ZooKeeper Leader选举:利用临时顺序节点,强一致但较重。
- 数据库排他锁:通过唯一索引或
SELECT ... FOR UPDATE实现,简单但性能瓶颈明显。
// Redis分布式单例管理器
public class DistributedSingletonManager {
private final RedissonClient redisson;
public <T> T getOrCreate(String key, Supplier<T> factory) {
RLock lock = redisson.getLock("singleton:" + key);
try {
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
// 双重检查,确保只创建一次
T existing = getFromCache(key);
if (existing != null) return existing;
T instance = factory.get();
putToCache(key, instance);
return instance;
}
} finally {
if (lock.isHeldByCurrentThread()) lock.unlock();
}
return getFromCache(key); // 未获取到锁时返回已有实例
}
}
局限:分布式单例本质上是一个共识问题,任何基于网络的方案都存在网络分区时的脑裂风险。
6.2 微服务客户端产品族的抽象工厂设计
在微服务架构中,一个应用可能同时需要调用Dubbo、gRPC、Feign等多种协议的远程服务。我们可以使用抽象工厂模式统一客户端创建接口:
// 抽象产品:RPC客户端
interface RpcClient {
<T> T createProxy(Class<T> serviceClass);
}
// 具体产品:Dubbo客户端
class DubboClient implements RpcClient { ... }
// 具体产品:gRPC客户端
class GrpcClient implements RpcClient { ... }
// 抽象工厂:RPC客户端工厂
interface RpcClientFactory {
RpcClient createClient(ServiceConfig config);
}
// 使用示例
RpcClientFactory factory = getFactory(protocol); // 根据配置选择具体工厂
RpcClient client = factory.createClient(config);
6.3 分布式配置对象的建造者模式应用
分布式配置中心(如Apollo、Nacos)的配置项往往数量众多且存在校验依赖。
public class DistributedConfig {
private final String dbUrl;
private final int maxPoolSize;
private final int minIdle;
private DistributedConfig(Builder builder) {
this.dbUrl = builder.dbUrl;
this.maxPoolSize = builder.maxPoolSize;
this.minIdle = builder.minIdle;
}
public static class Builder {
private String dbUrl;
private int maxPoolSize = 10;
private int minIdle = 2;
public Builder fromRemote(String configKey) {
// 从配置中心拉取原始配置
Map<String, Object> remote = ConfigCenter.get(configKey);
this.dbUrl = (String) remote.get("db.url");
this.maxPoolSize = (Integer) remote.get("pool.max");
return this;
}
public DistributedConfig build() {
// 分布式环境下的配置校验
if (minIdle > maxPoolSize) {
throw new IllegalStateException("minIdle cannot exceed maxPoolSize");
}
return new DistributedConfig(this);
}
}
}
6.4 跨服务DTO的原型模式快速复制
在微服务调用链中,DTO对象经常需要在不同服务间传递。为避免意外修改导致的数据污染,通常需要对DTO进行深拷贝。
// 原型模式:每个DTO实现Cloneable,提供深克隆
public class OrderDTO implements Cloneable {
private String orderId;
private List<OrderItemDTO> items;
@Override
public OrderDTO clone() {
OrderDTO clone = new OrderDTO();
clone.orderId = this.orderId;
clone.items = this.items.stream()
.map(OrderItemDTO::clone)
.collect(Collectors.toList());
return clone;
}
}
// 在服务边界使用
OrderDTO original = orderService.getOrder(id);
OrderDTO copyForAudit = original.clone(); // 独立副本,避免污染
auditService.audit(copyForAudit);
6.5 分布式对象工厂的资源协调
以分布式ID生成器为例,工厂方法可以封装具体的ID生成策略(雪花算法、号段模式、Leaf等)。
public interface IdGenerator {
long nextId();
}
public class IdGeneratorFactory {
public static IdGenerator create(String type, DataSource dataSource) {
switch (type) {
case "snowflake": return new SnowflakeIdGenerator(workerId);
case "segment": return new SegmentIdGenerator(dataSource);
case "leaf": return new LeafIdGenerator(leafConfig);
default: throw new IllegalArgumentException();
}
}
}
6.6 综合案例:多租户数据源路由 + 微服务客户端切换 + 配置中心驱动
以下架构展示了创建型模式在复杂分布式场景下的协同应用。
flowchart TD
subgraph 配置中心驱动层
A[Apollo/Nacos配置中心]
end
subgraph 抽象工厂层
B[DataSourceFactory<br/>数据源工厂]
C[RpcClientFactory<br/>RPC客户端工厂]
end
subgraph 具体产品族
D[TenantA数据源]
E[TenantB数据源]
F[Dubbo客户端]
G[gRPC客户端]
end
subgraph 建造者层
H[DataSourceBuilder<br/>数据源建造者]
I[RpcConfigBuilder<br/>RPC配置建造者]
end
subgraph 单例管理层
J[TenantContextHolder<br/>租户上下文单例]
K[ConnectionPoolManager<br/>连接池单例]
end
A -->|配置变更推送| B
A -->|配置变更推送| C
B --> H
C --> I
H -->|构建| D
H -->|构建| E
I -->|构建| F
I -->|构建| G
J -->|提供租户标识| B
J -->|提供租户标识| C
核心代码骨架:
// 1. 单例:租户上下文管理器(ThreadLocal隔离)
public enum TenantContext {
INSTANCE;
private final ThreadLocal<String> currentTenant = new ThreadLocal<>();
public void set(String tenant) { currentTenant.set(tenant); }
public String get() { return currentTenant.get(); }
public void clear() { currentTenant.remove(); }
}
// 2. 抽象工厂:动态数据源工厂
public interface DataSourceFactory {
DataSource createDataSource(String tenant);
}
// 3. 建造者:构建带连接池的Hikari数据源
public class HikariDataSourceBuilder {
private String url;
private String username;
private String password;
private int maxPoolSize = 20;
public HikariDataSourceBuilder fromConfigCenter(String tenant) {
Map<String, String> config = ConfigCenter.getTenantDbConfig(tenant);
this.url = config.get("url");
this.username = config.get("username");
this.password = config.get("password");
return this;
}
public HikariDataSource build() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(maxPoolSize);
return new HikariDataSource(config);
}
}
// 4. 抽象工厂实现:缓存租户数据源(单例模式确保每个租户只有一个数据源)
@Component
public class CachingDataSourceFactory implements DataSourceFactory {
private final Map<String, DataSource> cache = new ConcurrentHashMap<>();
@Override
public DataSource createDataSource(String tenant) {
return cache.computeIfAbsent(tenant, t ->
new HikariDataSourceBuilder().fromConfigCenter(t).build()
);
}
}
// 5. 路由切面
@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(TenantRouting)")
public Object route(ProceedingJoinPoint pjp) throws Throwable {
String tenant = extractTenant(pjp);
TenantContext.INSTANCE.set(tenant);
try {
return pjp.proceed();
} finally {
TenantContext.INSTANCE.clear();
}
}
}
架构解读:该设计综合运用了五种创建型模式。单例模式保障了租户上下文、数据源缓存池、连接池管理器的全局唯一性。抽象工厂模式提供了统一的数据源和RPC客户端创建接口,支持根据租户动态切换产品族。建造者模式封装了从配置中心拉取参数并构建复杂对象(如HikariDataSource)的繁琐过程。原型模式可用于跨线程DTO的安全拷贝。整个架构由配置中心驱动,实现了运行时动态创建与切换,是创建型模式在分布式系统中的一次集中检阅。
七、常见设计误区与反模式警示
模式是双刃剑,滥用比不用更可怕。
7.1 单例模式的滥用
误区表现:将所有需要全局访问的对象都设计为单例,导致全局状态污染、单例工厂成为“上帝类”。
// 反模式:单例工厂上帝类
public class GlobalServiceFactory {
private static GlobalServiceFactory instance = new GlobalServiceFactory();
public static GlobalServiceFactory getInstance() { return instance; }
public UserService getUserService() { ... }
public OrderService getOrderService() { ... }
public PaymentService getPaymentService() { ... }
// 数十个getter方法,严重违反单一职责
}
重构建议:使用依赖注入容器(如Spring IoC)管理Bean生命周期,将单例的控制权交给容器。
7.2 抽象工厂的过度设计
误区表现:产品族维度长期稳定(如只有一种数据库),却引入抽象工厂,徒增复杂度。
// 过度设计:只有MySQL一种产品族,却定义了抽象工厂
interface DatabaseFactory {
Connection createConnection();
Statement createStatement();
}
class MySQLFactory implements DatabaseFactory { ... }
重构建议:遵循YAGNI原则(You Aren't Gonna Need It),初期使用简单工厂,待确实出现多个产品族时再重构为抽象工厂。
7.3 建造者模式用于简单对象
误区表现:构造参数只有2-3个,却强行使用Builder。
// 反模式:过度使用Builder
public class Point {
private final int x, y;
private Point(Builder b) { this.x = b.x; this.y = b.y; }
public static class Builder {
private int x, y;
public Builder x(int x) { this.x = x; return this; }
public Builder y(int y) { this.y = y; return this; }
public Point build() { return new Point(this); }
}
}
// Point p = new Point.Builder().x(1).y(2).build(); // 比new Point(1,2)还繁琐
重构建议:当构造参数 ≤ 3 且无复杂校验时,直接使用构造函数或静态工厂方法。
7.4 原型模式的克隆深度失控
误区表现:深克隆时递归拷贝整个对象图,导致性能灾难或栈溢出。
// 反模式:粗暴的序列化深克隆
public Object deepClone() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
return ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
重构建议:明确哪些字段需要深拷贝,哪些可以浅拷贝或共享。对于大对象图,考虑使用拷贝-on-write 或不可变对象模式。
7.5 工厂方法泛滥
误区表现:每新增一个类都为其配套创建一个工厂类,导致“类爆炸”。
// 反模式:无意义的工厂
class UserFactory { public User create() { return new User(); } }
class OrderFactory { public Order create() { return new Order(); } }
重构建议:只有当对象的创建逻辑复杂(如依赖外部资源、需要缓存、需要动态选择子类)时才引入工厂。
八、面试题精选与专家级解答
1. 请用一句话概括五种创建型模式的核心区别
答:单例控数量,工厂方法延子类,抽象工厂管产品族,建造者理步骤,原型靠克隆。
2. Spring中BeanFactory和FactoryBean分别体现了哪些创建型模式?它们是如何协同工作的?
答:
BeanFactory:体现了工厂方法模式(getBean方法是工厂方法)和抽象工厂模式(ListableBeanFactory管理产品族)。FactoryBean:体现了工厂方法模式(getObject方法)和建造者模式(通过setter收集配置)。- 协同工作:
BeanFactory是容器的基础设施,负责管理所有Bean的生命周期;FactoryBean是一种特殊的Bean,其自身由BeanFactory管理,但它生产的对象(getObject()返回值)也交由BeanFactory管理。Spring在getBean时检测到Bean是FactoryBean,会调用其getObject()获取真正的产品对象。
3. 如何用创建型模式设计一个支持动态切换多数据库的持久层?
答:组合使用抽象工厂模式和单例模式。
- 定义抽象工厂接口
DatabaseFactory,包含createConnection()、createCommand()等方法。 - 为每种数据库(MySQL、PostgreSQL、Oracle)实现具体工厂。
- 使用一个单例的
DatabaseFactoryManager管理当前激活的工厂实例。 - 通过配置文件或动态切换方法,更换
DatabaseFactoryManager中的当前工厂引用,实现数据库切换。
4. 单例模式和原型模式在Spring中如何通过作用域体现?为何prototype Bean不支持循环依赖?
答:
- Spring通过
@Scope("singleton")(默认)和@Scope("prototype")体现两种模式。 - prototype不支持循环依赖的原因:Spring解决单例循环依赖的核心是三级缓存。当A依赖B,B依赖A时,Spring可以先创建A的早期引用(尚未填充属性)放入缓存,然后创建B,B需要A时从缓存获取该早期引用。但prototype Bean不会被缓存,每次
getBean都创建新实例。如果A和B都是prototype且相互依赖,getBean(A)会触发getBean(B),而getBean(B)又会触发getBean(A),形成无限递归,最终栈溢出。
5. 建造者模式和抽象工厂模式都可以构建复杂对象,它们的本质区别是什么?
答:
- 抽象工厂模式关注产品族的创建,强调“一次性返回一组完整的产品”,客户端不关心构建过程。
- 建造者模式关注单个复杂对象的分步骤构建,强调“相同的构建过程可以产生不同的表示”,客户端可能参与指导构建步骤。
6. 工厂方法模式遵循开闭原则,但抽象工厂模式新增产品等级结构时却需要修改抽象工厂接口,这是否违背开闭原则?如何权衡?
答:确实违背了开闭原则。抽象工厂模式在产品族维度上符合开闭原则(新增产品族只需新增具体工厂),但在产品等级结构维度上违反开闭原则(新增产品需要修改抽象接口)。这是模式的固有局限性。权衡之道:若系统在产品族维度上更稳定,而在产品等级结构上更易变,应考虑使用工厂方法模式而非抽象工厂模式;反之则使用抽象工厂。另一种缓解方法是结合原型模式,让抽象工厂返回原型对象而非具体类。
7. 在分布式环境下,如何利用创建型模式设计一个统一的RPC客户端工厂,同时支持多种通信协议?
答:使用抽象工厂模式结合建造者模式。
- 定义抽象工厂
RpcClientFactory,包含方法createClient(ProtocolConfig config)。 - 为Dubbo、gRPC、Feign等分别实现具体工厂。
- 每个具体工厂内部使用建造者模式构建复杂的客户端配置对象(如
DubboBootstrap)。 - 客户端通过配置中心获取当前协议类型,动态选择具体工厂实例。
8. 原型模式中的深克隆和浅克隆在分布式缓存场景下分别适用于什么情况?
答:
- 浅克隆:适用于缓存对象内部引用的都是不可变对象(如String、基本类型包装类),或引用的对象本身就是共享的且不会被修改(如全局配置对象)。性能高,但需谨慎处理引用。
- 深克隆:适用于缓存对象与业务逻辑之间存在数据隔离要求。例如从缓存中取出一个用户DTO,后续需要修改其属性,为避免污染缓存中的原始对象,必须使用深克隆获得独立副本。
9. 请设计一个线程安全的懒加载单例,并分析其与Spring单例Bean的异同
答:
public class ThreadSafeLazySingleton {
private static volatile ThreadSafeLazySingleton instance;
private ThreadSafeLazySingleton() {
// 防止反射攻击
if (instance != null) throw new IllegalStateException();
}
public static ThreadSafeLazySingleton getInstance() {
if (instance == null) {
synchronized (ThreadSafeLazySingleton.class) {
if (instance == null) {
instance = new ThreadSafeLazySingleton();
}
}
}
return instance;
}
// 防止反序列化破坏单例
protected Object readResolve() {
return getInstance();
}
}
与Spring单例Bean的异同:
- 相同点:都保证在特定范围内(JVM或容器)只有一个实例。
- 不同点:Spring的单例是容器管理的单例,一个类在同一个
ApplicationContext中是单例,但在不同的ApplicationContext中可以存在多个实例。Spring的单例是注册式单例,而非通过私有构造器强制全局唯一,因此可以被继承、被AOP代理。
10. 静态工厂方法与工厂方法模式在JDK中都有大量应用,它们的本质区别是什么?
答:
- 静态工厂方法(如
Integer.valueOf()):是类级别的静态方法,不具备多态性,无法通过继承改变创建行为。它是一种创建技巧而非设计模式。 - 工厂方法模式(如
Calendar.getInstance()):是对象级别的实例方法(或抽象方法),依赖继承和多态,子类可以重写工厂方法以返回不同的产品。
11. 如何用建造者模式重构一个包含20+字段的配置类,并保证构建出的对象不可变?
答:核心是私有构造器 + 内部静态Builder类。
public class ComplexConfig {
private final String field1;
private final int field2;
// ... 18 more fields
private ComplexConfig(Builder builder) {
this.field1 = builder.field1;
this.field2 = builder.field2;
// ...
}
public static class Builder {
private String field1;
private int field2;
// ...
public Builder field1(String val) { field1 = val; return this; }
public Builder field2(int val) { field2 = val; return this; }
public ComplexConfig build() {
// 集中校验
validate();
return new ComplexConfig(this);
}
private void validate() { /* 复杂校验逻辑 */ }
}
}
12. 创建型模式中哪些模式可以与享元模式组合使用?请举例说明
答:
- 单例模式 + 享元模式:单例的享元工厂,管理可共享的享元对象池。
- 工厂方法模式 + 享元模式:工厂方法返回享元对象,内部逻辑维护对象池。
- 原型模式 + 享元模式:原型作为享元对象的模板,克隆生成具有特定外部状态的对象。例如文本编辑器中的字符格式(享元)与具体字符(外部状态)。
13. 在DDD领域驱动设计中,工厂模式与仓储模式(Repository)有何关系?
答:
- 工厂模式负责创建新的领域对象(如
UserFactory.create()),关注对象生命周期的开始。 - 仓储模式负责重建已持久化的领域对象(如
userRepository.findById(id)),关注对象生命周期的中间。 - 两者共同构成领域对象的生命周期管理入口。通常,工厂是纯粹的领域逻辑,而仓储会涉及基础设施(数据库、缓存)。
14. 为什么说IoC容器是创建型模式的集大成者?请从设计意图角度分析
答:IoC容器的核心职责是对象的创建与装配,这正是创建型模式的共同使命。Spring IoC容器:
- 通过
BeanFactory实现了工厂方法与抽象工厂。 - 通过作用域(
singleton/prototype)实现了单例模式与原型模式。 - 通过
BeanDefinitionBuilder和属性填充机制体现了建造者模式的思想。 - IoC容器将这些模式内聚为一个协调的整体,让开发者可以声明式地描述对象间的依赖关系,而无需手动调用工厂或构造器,将创建型模式的威力发挥到了极致。
15. 五种创建型模式中,哪些是类模式(依赖继承),哪些是对象模式(依赖组合)?这对扩展性有何影响?
答:
- 类模式(依赖继承):工厂方法模式。通过子类重写工厂方法来改变创建行为。扩展性受限于单继承,但静态类型安全。
- 对象模式(依赖组合):抽象工厂模式、建造者模式、原型模式、单例模式。通过组合对象(策略对象、建造者实例、原型实例)来改变行为。扩展性更强,可在运行时动态替换,符合“组合优于继承”原则。
结语
创建型模式是面向对象设计的基石,它们将“变化”隔离在对象创建的边界之内,为系统的可扩展性、可维护性与性能提供了坚实的基础。从单例的数量约束,到工厂家族的灵活生产,再到建造者的精雕细琢与原型的快速复制,五种模式各有疆域却又相互关联。在Spring框架中,我们看到了它们被升华、融合并应用于工业级产品的优雅实践;在分布式架构中,它们经受住了规模与复杂度的考验,展现出持久的生命力。
理解模式只是起点,掌握何时使用、何时克制,才是设计师真正的修炼。愿本文能成为您创建型模式知识体系的收官之作,助您在软件设计的征途上,更加游刃有余。