Appearance
第75课:创建型模式
🎯 学习目标
- 理解创建型模式解决的是“对象如何创建、谁负责创建、创建过程如何变化”的问题。
- 掌握单例、工厂方法、抽象工厂、建造者、原型模式的核心思想。
- 能识别 Java 标准库和 Spring 中的创建型模式。
- 能判断什么时候使用模式,什么时候直接
new更好。 - 避免把设计模式写成复杂炫技代码。
📖 一、创建型模式解决什么问题
最简单的对象创建方式是:
java
UserService service = new UserService();这没有问题。只有当创建逻辑开始变化或复杂时,才需要创建型模式:
text
对象创建步骤很多
对象依赖不同配置
需要隐藏具体实现类
需要控制实例数量
需要根据运行时条件选择实现
创建成本很高,需要复用或复制设计模式的目标不是“消灭 new”,而是把变化点集中管理。
📖 二、单例模式
单例保证一个类在 JVM 内只有一个实例,并提供全局访问点。
1. 推荐写法:枚举单例
java
public enum IdGenerator {
INSTANCE;
private final AtomicLong counter = new AtomicLong();
public long nextId() {
return counter.incrementAndGet();
}
}使用:
java
long id = IdGenerator.INSTANCE.nextId();枚举单例天然防反射、防反序列化破坏。
2. 静态内部类写法
java
public class ConfigManager {
private ConfigManager() {
}
private static class Holder {
private static final ConfigManager INSTANCE = new ConfigManager();
}
public static ConfigManager getInstance() {
return Holder.INSTANCE;
}
}利用类加载机制实现延迟加载和线程安全。
3. Spring 中的单例
Spring Bean 默认是 singleton,但它是 Spring 容器内单例,不等于整个 JVM 绝对单例。
java
@Service
public class OrderService {
}多数企业项目中,不需要自己写单例模式。把无状态服务交给 Spring 管理即可。
📖 三、简单工厂
简单工厂不是 GoF 正式模式,但很常用。
java
public interface PaymentService {
void pay(PaymentCommand command);
}
public class AlipayService implements PaymentService {
public void pay(PaymentCommand command) {
// 支付宝支付
}
}
public class WechatPayService implements PaymentService {
public void pay(PaymentCommand command) {
// 微信支付
}
}工厂:
java
public class PaymentFactory {
public static PaymentService create(String type) {
return switch (type) {
case "ALIPAY" -> new AlipayService();
case "WECHAT" -> new WechatPayService();
default -> throw new IllegalArgumentException("Unsupported payment type: " + type);
};
}
}适用场景:
text
实现类较少
创建逻辑简单
调用方不想知道具体实现类缺点是新增类型要修改工厂类,违反开闭原则。
📖 四、工厂方法模式
工厂方法把创建逻辑延迟到子类或具体工厂。
java
public interface Notification {
void send(String message);
}
public class EmailNotification implements Notification {
public void send(String message) {
System.out.println("send email: " + message);
}
}
public class SmsNotification implements Notification {
public void send(String message) {
System.out.println("send sms: " + message);
}
}工厂接口:
java
public interface NotificationFactory {
Notification create();
}
public class EmailNotificationFactory implements NotificationFactory {
public Notification create() {
return new EmailNotification();
}
}
public class SmsNotificationFactory implements NotificationFactory {
public Notification create() {
return new SmsNotification();
}
}新增通知类型时,新建实现和工厂,不需要修改已有工厂代码。
在 Spring 中,更常见的写法是把多个实现注入为 Map:
java
@Service
public class PaymentRouter {
private final Map<String, PaymentService> services;
public PaymentRouter(Map<String, PaymentService> services) {
this.services = services;
}
public PaymentService route(String beanName) {
PaymentService service = services.get(beanName);
if (service == null) {
throw new IllegalArgumentException("unsupported payment: " + beanName);
}
return service;
}
}Spring 容器本身承担了部分工厂职责。
📖 五、抽象工厂模式
抽象工厂创建一组相关对象。
场景:不同云厂商有不同的存储、消息、监控客户端。
java
public interface CloudFactory {
StorageClient storageClient();
MessageClient messageClient();
}
public class AwsCloudFactory implements CloudFactory {
public StorageClient storageClient() {
return new S3StorageClient();
}
public MessageClient messageClient() {
return new SqsMessageClient();
}
}
public class AliyunCloudFactory implements CloudFactory {
public StorageClient storageClient() {
return new OssStorageClient();
}
public MessageClient messageClient() {
return new MnsMessageClient();
}
}它强调“产品族一致性”:
text
AWS Storage + AWS Message
Aliyun Storage + Aliyun Message避免混用不同厂商组件。
📖 六、建造者模式
建造者适合创建参数多、可选项多、希望对象不可变的场景。
java
public class HttpRequestOptions {
private final int connectTimeout;
private final int readTimeout;
private final int maxRetries;
private final Map<String, String> headers;
private HttpRequestOptions(Builder builder) {
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.maxRetries = builder.maxRetries;
this.headers = Map.copyOf(builder.headers);
}
public static class Builder {
private int connectTimeout = 1000;
private int readTimeout = 3000;
private int maxRetries = 3;
private Map<String, String> headers = new HashMap<>();
public Builder connectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
return this;
}
public Builder readTimeout(int readTimeout) {
this.readTimeout = readTimeout;
return this;
}
public Builder header(String key, String value) {
this.headers.put(key, value);
return this;
}
public HttpRequestOptions build() {
return new HttpRequestOptions(this);
}
}
}使用:
java
HttpRequestOptions options = new HttpRequestOptions.Builder()
.connectTimeout(2000)
.readTimeout(5000)
.header("X-App", "order-service")
.build();Java 中很多 API 使用建造者,例如 StringBuilder、HttpClient.newBuilder()、Lombok @Builder。
📖 七、原型模式
原型模式通过复制已有对象创建新对象。
java
public class ReportTemplate implements Cloneable {
private String title;
private List<String> sections;
@Override
public ReportTemplate clone() {
try {
ReportTemplate copy = (ReportTemplate) super.clone();
copy.sections = new ArrayList<>(this.sections);
return copy;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}重点是深拷贝和浅拷贝:
text
浅拷贝:复制对象本身,引用字段仍指向同一对象。
深拷贝:引用字段也复制出新对象。实际项目中,原型模式常用于:
text
配置模板复制
表单模板复制
对象创建成本高,需要基于模板生成⚠️ 八、常见陷阱
1. 为了模式而模式
如果创建逻辑只有一行 new,没有变化点,不需要工厂。
2. 单例保存可变业务状态
Spring Service 是单例,如果在字段里保存请求级数据,会产生线程安全问题。
错误示例:
java
@Service
public class OrderService {
private Long currentUserId;
}3. Builder 绕过校验
建造者最终 build() 时仍要校验必要字段。
4. clone 造成共享引用
原型模式里最容易漏掉集合、数组、嵌套对象的深拷贝。
🆚 九、Java vs C 对比
| 维度 | C | Java |
|---|---|---|
| 对象创建 | malloc、struct 初始化 | 构造器、工厂、Spring 容器 |
| 单例 | 静态变量、全局对象 | enum、静态内部类、Spring Bean |
| 建造者 | 手写初始化函数 | Builder 链式 API |
| 原型复制 | memcpy/手写拷贝 | clone、拷贝构造、序列化 |
Java 的创建型模式和类型系统、反射、容器关系紧密,尤其 Spring 本身就是大型对象工厂。
💡 十、最佳实践
- 没有变化点时直接
new,不要过度设计。 - Spring 项目中优先让容器管理无状态服务。
- 多实现路由可以用 Spring 注入
Map<String, Interface>。 - 参数多且对象应不可变时使用 Builder。
- 单例不要保存请求级可变状态。
- 原型模式必须明确深拷贝策略。
- 工厂负责创建对象,不要塞入过多业务逻辑。
🎓 小结
创建型模式关注“对象从哪里来”。它们的价值是隔离创建复杂度、隐藏具体实现、保证产品族一致性或控制实例数量。判断是否需要创建型模式的关键,是对象创建是否真的存在变化和复杂度。