1. 概述

本文将深入探讨创建型设计模式中的一种——原型模式(Prototype Pattern)。我们不会停留在理论层面,而是结合 Java 实际编码,手把手实现该模式的核心逻辑。

同时,也会分析它的适用场景、优势与踩坑点。对于有经验的开发者来说,这不仅仅是一个“复制对象”的技巧,而是一种在特定场景下提升性能和解耦的利器。

2. 原型模式详解

原型模式的核心思想非常简单粗暴:当你已经有一个对象实例(即原型)时,后续创建同类对象不再通过 new 关键字构造,而是直接“克隆”这个原型

举个游戏开发中的常见场景:

假设你正在开发一个 2D 游戏,地图背景需要大量树木。如果每次渲染都 new 一棵树,不仅效率低,而且每棵树的初始化参数(如高度、质量)可能还都一样。

更好的做法是:

  1. 先创建一棵“模板树”(原型)
  2. 需要新树时,直接克隆这棵树
  3. 只需调整位置、颜色等个别属性即可

这样做的好处是:避免重复的构造逻辑,提升性能,还能动态调整原型状态。

核心价值

  • 隐藏对象创建细节
  • 避免复杂的构造函数调用
  • 支持运行时动态配置原型

3. UML 类图

Prototype Pattern

从图中可以看出:

  • Prototype 是一个接口,声明了 clone() 方法
  • ConcretePrototype1ConcretePrototype2 实现了具体的克隆逻辑
  • Client 不关心具体类型,只需调用 clone() 即可获得新实例

这种设计完美体现了面向接口编程多态性的优势。

4. Java 实现方式

Java 中实现原型模式有多种方式,最常见的是使用 Cloneable 接口 + clone() 方法。但要注意⚠️:

Cloneable 是一个标记接口,如果不实现它而直接调用 clone(),会抛出 CloneNotSupportedException

4.1 关键决策:浅拷贝 vs 深拷贝

在实现克隆时,必须明确选择:

类型 适用场景 风险
✅ 浅拷贝 对象只包含基本类型或不可变对象(如 String、Integer) 引用字段共用,修改会影响所有克隆体
✅ 深拷贝 包含可变引用字段(如 List、自定义对象) 实现复杂,可能需递归拷贝

推荐方案:

  • 使用拷贝构造函数(Copy Constructor)
  • 或通过序列化/反序列化实现深拷贝(适用于可序列化的类)

4.2 不依赖 Cloneable 的实现(推荐)

为了避免 clone() 的各种坑(比如 protected 访问限制、异常处理等),我们可以自己定义 copy() 方法。

先定义一个抽象基类:

public abstract class Tree {
    
    private double mass;
    private double height;
    private Position position;

    // 构造函数、getter/setter 省略...

    public abstract Tree copy();
}

然后实现具体子类:

public class PlasticTree extends Tree {

    public PlasticTree(double mass, double height) {
        this.mass = mass;
        this.height = height;
    }

    @Override
    public Tree copy() {
        PlasticTree plasticTreeClone = new PlasticTree(this.getMass(), this.getHeight());
        plasticTreeClone.setPosition(this.getPosition());
        return plasticTreeClone;
    }
}
public class PineTree extends Tree {

    public PineTree(double mass, double height) {
        this.mass = mass;
        this.height = height;
    }

    @Override
    public Tree copy() {
        PineTree pineTreeClone = new PineTree(this.getMass(), this.getHeight());
        pineTreeClone.setPosition(this.getPosition());
        return pineTreeClone;
    }
}

这种方式的优势

  • 完全掌控拷贝逻辑
  • 避免 Cloneable 的诡异行为
  • 易于实现深拷贝
  • 更符合现代 Java 编程习惯

5. 测试验证

5.1 单个对象克隆测试

public class TreePrototypesUnitTest {

    @Test
    public void givenAPlasticTreePrototypeWhenClonedThenCreateA_Clone() {
        double mass = 10.0;
        double height = 5.0;
        Position position = new Position(0, 0);
        Position otherPosition = new Position(10, 10);

        PlasticTree plasticTree = new PlasticTree(mass, height);
        plasticTree.setPosition(position);
        
        PlasticTree anotherPlasticTree = (PlasticTree) plasticTree.copy();
        anotherPlasticTree.setPosition(otherPosition);

        assertEquals(position, plasticTree.getPosition());
        assertEquals(otherPosition, anotherPlasticTree.getPosition());
    }
}

测试结果表明:克隆后的对象是独立实例,修改位置不会影响原对象。

5.2 批量克隆测试(体现多态优势)

@Test
public void givenA_ListOfTreesWhenClonedThenCreateListOfClones() {
    double mass = 10.0;
    double height = 5.0;
    Position position = new Position(0, 0);

    PlasticTree plasticTree = new PlasticTree(mass, height);
    plasticTree.setPosition(position);

    PineTree pineTree = new PineTree(mass, height);
    pineTree.setPosition(position);

    List<Tree> trees = Arrays.asList(plasticTree, pineTree);
    List<Tree> treeClones = trees.stream().map(Tree::copy).collect(toList());

    Tree plasticTreeClone = treeClones.get(0);
    Tree pineTreeClone = treeClones.get(1);

    assertEquals(height, plasticTreeClone.getHeight());
    assertEquals(position, plasticTreeClone.getPosition());
}

⚠️ 亮点:客户端代码完全不知道具体类型,通过多态统一调用 copy(),实现了对扩展开放,对修改关闭

6. 优缺点分析

✅ 优势

  • 性能优化:避免重复初始化,尤其适合构造成本高的对象
  • 解耦创建逻辑:客户端无需知道具体类名
  • 动态配置:可在运行时修改原型状态,影响后续克隆结果
  • 简化复杂对象创建:相比工厂模式,更适合“微调复制”场景

❌ 缺点

  • 深拷贝实现复杂:涉及嵌套对象、循环引用时容易出错
  • 难以处理 final 字段:某些情况下无法在克隆中重新赋值
  • 过度设计风险:简单对象没必要用原型模式
  • 状态一致性问题:若原型被意外修改,会影响所有未来克隆体

7. 总结

原型模式是一种实用且高效的创建型模式,特别适用于:

  • 对象初始化开销大
  • 对象状态组合有限
  • 需要大量相似对象的场景(如游戏、GUI 组件)

虽然 Java 提供了 Cloneable 接口,但实际开发中更推荐自定义 copy() 方法,避免其固有的缺陷。

源码已托管至 GitHub:https://github.com/baeldung/design-patterns-creational

合理使用原型模式,能让你的代码更简洁、性能更高。但在使用前务必评估是否真的需要——毕竟,最简单的方案往往是最好的。


原始标题:Prototype Pattern in Java | Baeldung