原型模式

你做过报表系统吗?有一种常见的优化策略:报表模板只创建一次,然后复制多份给不同用户使用。

// 每次都从数据库加载模板
ReportTemplate template1 = loadTemplateFromDB();
ReportTemplate template2 = loadTemplateFromDB();
ReportTemplate template3 = loadTemplateFromDB();

数据库加载 IO 成本很高,如果能复制已有模板而不需要重复查询,性能会好很多。原型模式就是来解决这个问题的:通过复制已有对象来创建新对象,而不是通过构造函数

深拷贝 vs 浅拷贝的实际案例

假设有一个订单系统:

public class Order {
    private String orderId;
    private List<OrderItem> items;  // 订单项列表
    private Customer customer;      // 客户信息

    // 构造函数、getter、setter
}

如果用 Objectclone() 方法(如果没有正确实现),会发生什么?

Order original = new Order("O001", items, customer);
Order copy = (Order) original.clone();

// 修改原订单的商品
original.getItems().add(new OrderItem());

// 拷贝的订单也变了!❌
System.out.println(copy.getItems().size()); // 多了 1 个

这就是浅拷贝的问题:只复制了引用,没有复制引用指向的实际对象。

Cloneable 接口的问题

Object.clone() 是 Java 提供的原生克隆方法,但它的设计有缺陷:

  1. 权限问题clone()protected 方法,需要强制转型
  2. 默认实现是浅拷贝:基本类型复制值,引用类型复制引用
  3. 必须实现 Cloneable 接口:否则抛出 CloneNotSupportedException
  4. 契约不明确Cloneable 接口没有任何方法,它只是一个标记接口
public class Order implements Cloneable {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // 浅拷贝
    }
}

更好的做法是自定义拷贝方法,而不是依赖 clone()

深拷贝实现方式

方式一:序列化拷贝

public class Order implements Serializable {
    private String orderId;
    private List<OrderItem> items;
    private Customer customer;

    public Order deepClone() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (Order) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Deep clone failed", e);
        }
    }
}

优点:实现简单,递归复制所有引用类型 缺点:性能开销大(序列化/反序列化);所有类都要实现 Serializable

方式二:递归拷贝

public class Order {
    private String orderId;
    private List<OrderItem> items;
    private Customer customer;

    public Order deepClone() {
        Order copy = new Order();
        copy.orderId = this.orderId;
        copy.items = new ArrayList<>();
        for (OrderItem item : this.items) {
            copy.items.add(item.clone());
        }
        copy.customer = this.customer.clone();
        return copy;
    }
}

public class OrderItem implements Cloneable {
    private String productId;
    private int quantity;

    @Override
    public OrderItem clone() {
        OrderItem copy = new OrderItem();
        copy.productId = this.productId;
        copy.quantity = this.quantity;
        return copy;
    }
}

优点:性能好;类型安全 缺点:每个类都要实现克隆逻辑;如果引用类型变化,克隆逻辑需要同步更新

方式三:JSON 序列化(第三方库)

import com.fasterxml.jackson.databind.ObjectMapper;

public class Order {
    private String orderId;
    private List<OrderItem> items;
    private Customer customer;

    public Order deepClone() {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(mapper.writeValueAsString(this), Order.class);
    }
}

优点:实现简单;处理循环引用;JSON 可读性好 缺点:需要第三方库;性能比直接递归拷贝差

原型管理器

当原型对象较多时,可以使用原型管理器统一管理:

public class PrototypeRegistry<T> {
    private final Map<String, T> prototypes = new HashMap<>();

    public void addPrototype(String key, T prototype) {
        prototypes.put(key, prototype);
    }

    public T getPrototype(String key) {
        T prototype = prototypes.get(key);
        if (prototype == null) {
            throw new IllegalArgumentException("Prototype not found: " + key);
        }
        try {
            return (T) prototype.getClass().getMethod("clone").invoke(prototype);
        } catch (Exception e) {
            throw new RuntimeException("Clone failed", e);
        }
    }

    public void remove(String key) {
        prototypes.remove(key);
    }
}
// 使用
PrototypeRegistry<ReportTemplate> registry = new PrototypeRegistry<>();

registry.addPrototype("sales", new SalesReportTemplate());
registry.addPrototype("inventory", new InventoryReportTemplate());

// 复制模板
ReportTemplate salesReport = registry.getPrototype("sales");

原型模式 vs 工厂模式

维度原型模式工厂模式
创建方式复制已有对象创建新对象
依赖关系不需要知道具体类依赖工厂类
复杂度需要实现克隆方法需要工厂类
性能高(避免重复初始化)一般
适用场景创建成本高、结构复杂创建成本低、产品稳定

典型应用场景

1. 性能优化

// 不推荐:每次都查询数据库
Report createReport(ReportTemplate template) {
    ReportTemplate t = loadTemplateFromDB();  // 慢
    return buildReport(t);
}

// 推荐:使用原型复制
Report createReport(ReportTemplate template) {
    ReportTemplate t = template.clone();  // 快
    return buildReport(t);
}

2. 配置复制

// 游戏开发中的怪物模板
MonsterTemplate goblinTemplate = loadMonsterTemplate("Goblin");
Monster goblin1 = goblinTemplate.clone();
Monster goblin2 = goblinTemplate.clone();

3. 撤销/重做功能

// 保存当前状态快照
DocumentSnapshot snapshot = currentDocument.clone();
history.push(snapshot);

// 恢复
DocumentSnapshot previous = history.pop();
currentDocument = previous.clone();

反模式警示

1. 滥用浅拷贝

// 错误:没有意识到是浅拷贝
Order copy = (Order) original.clone();
copy.getItems().clear();  // 原订单也清空了!

2. 深拷贝中的循环引用

public class Employee {
    private String name;
    private Employee manager;  // 循环引用!

    @Override
    public Employee clone() {
        Employee copy = new Employee();
        copy.name = this.name;
        copy.manager = this.manager.clone();  // 无限递归!
        return copy;
    }
}

需要使用「记录已拷贝对象」的技术来处理循环引用。

思考题

问题 1:为什么说 Cloneable 接口是一个「坏设计」?

参考答案

Cloneable 接口的设计问题:

  1. 标记接口没有方法Cloneable 本身是空的,只是标记「此类允许克隆」。这违反了接口的设计原则——接口应该定义行为,而不是权限。

  2. clone()Object 中定义clone()protected 方法,返回值是 Object。调用方需要强制转型,且无法以类型安全的方式使用。

  3. 默认实现是危险的:默认的 super.clone() 是浅拷贝,容易引发 bug。但没人会主动去重写它。

  4. clone() 契约复杂Object.clone() 的规范要求很多:长度必须相同、所有元素相等、返回的对象应该是正确的类型……但 Cloneable 接口无法强制这些要求。

更好的设计应该是显式定义克隆方法,如 copy()deepCopy(),而不是依赖一个标记接口。

问题 2:深拷贝中如何处理循环引用?

参考答案

以 Employee 部门树为例:

public class Employee {
    private String name;
    private Employee manager;
    private List<Employee> subordinates;

    public Employee deepClone() {
        Map<Employee, Employee> cloneMap = new IdentityHashMap<>();
        return deepClone(cloneMap);
    }

    private Employee deepClone(Map<Employee, Employee> cloneMap) {
        if (cloneMap.containsKey(this)) {
            return cloneMap.get(this);
        }

        Employee copy = new Employee();
        copy.name = this.name;
        cloneMap.put(this, copy);  // 先放入,防止循环引用

        copy.manager = this.manager == null ? null : this.manager.deepClone(cloneMap);

        copy.subordinates = new ArrayList<>();
        for (Employee sub : this.subordinates) {
            copy.subordinates.add(sub.deepClone(cloneMap));
        }

        return copy;
    }
}

核心思想:使用 IdentityHashMap 记录「已拷贝的对象」,遇到循环引用时直接返回已有的拷贝,而不是创建新对象。

问题 3:原型模式在 Spring 中有应用吗?

参考答案

Spring 的 @Scope("prototype") 就是原型模式的应用。

@Component
@Scope("prototype")
public class MyBean {
    // 每次注入都创建新实例
}

Spring 默认是单例模式(所有 Bean 只创建一次),对于需要每次注入新实例的 Bean,使用 prototype 作用域。

@Configuration
public class AppConfig {
    @Bean
    @Scope("prototype")
    public OrderProcessor orderProcessor() {
        return new OrderProcessor();
    }
}

注意:Spring 不会管理原型 Bean 的完整生命周期。销毁回调等不会执行,需要手动处理。