Skip to content

第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 使用建造者,例如 StringBuilderHttpClient.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 对比

维度CJava
对象创建malloc、struct 初始化构造器、工厂、Spring 容器
单例静态变量、全局对象enum、静态内部类、Spring Bean
建造者手写初始化函数Builder 链式 API
原型复制memcpy/手写拷贝clone、拷贝构造、序列化

Java 的创建型模式和类型系统、反射、容器关系紧密,尤其 Spring 本身就是大型对象工厂。


💡 十、最佳实践

  • 没有变化点时直接 new,不要过度设计。
  • Spring 项目中优先让容器管理无状态服务。
  • 多实现路由可以用 Spring 注入 Map&lt;String, Interface&gt;
  • 参数多且对象应不可变时使用 Builder。
  • 单例不要保存请求级可变状态。
  • 原型模式必须明确深拷贝策略。
  • 工厂负责创建对象,不要塞入过多业务逻辑。

🎓 小结

创建型模式关注“对象从哪里来”。它们的价值是隔离创建复杂度、隐藏具体实现、保证产品族一致性或控制实例数量。判断是否需要创建型模式的关键,是对象创建是否真的存在变化和复杂度。