← 返回题目列表

如何手写一个原型模式?Java 代码怎么实现?

高频 中等 第 4 / 25 题 更新于 2026/07/28
原型模式JavaCloneablecopy代码实现

简化版

手写原型模式可以定义一个 copy() 方法,让对象自己返回一个新副本。Java 里也可以用 Cloneableclone(),但实际项目更推荐显式写拷贝构造器或 copy() 方法,因为可读性更好,也更容易控制深拷贝逻辑。

详细版

一种清晰实现方式:

interface Prototype<T> {
    T copy();
}

class UserTemplate implements Prototype<UserTemplate> {
    private String role;
    private List<String> permissions;

    public UserTemplate copy() {
        UserTemplate copy = new UserTemplate();
        copy.role = this.role;
        copy.permissions = new ArrayList<>(this.permissions);
        return copy;
    }
}

也可以用 Java 自带 clone()

class UserTemplate implements Cloneable {
    protected UserTemplate clone() throws CloneNotSupportedException {
        return (UserTemplate) super.clone();
    }
}

super.clone() 默认是浅拷贝,可变引用需要手动深拷贝。工程里更建议把复制意图写清楚,避免隐藏 bug。

完整版教学

一、先定义自己的复制接口

比起直接使用 Cloneable,可以先写一个业务语义更清楚的接口:

interface Copyable<T> {
    T copy();
}

然后让具体类实现:

class ReportTemplate implements Copyable<ReportTemplate> {
    private String type;
    private List<String> columns;

    public ReportTemplate copy() {
        ReportTemplate copy = new ReportTemplate();
        copy.type = this.type;
        copy.columns = new ArrayList<>(this.columns);
        return copy;
    }
}

这个写法直观、可控,也不用处理 CloneNotSupportedException

二、为什么要由对象自己复制自己

复制逻辑放在类内部更合适,因为类最了解自己的字段含义。

例如:

private Long id;
private String name;
private List<String> permissions;
private LocalDateTime createdAt;

复制时:

  • id 可能要重新生成;
  • name 可以复制;
  • permissions 可能要复制新列表;
  • createdAt 可能要设置当前时间。

外部工具很难准确理解这些业务语义。

三、使用 Cloneable 的写法

Java 也支持这种写法:

class ReportTemplate implements Cloneable {
    private String type;
    private List<String> columns;

    @Override
    protected ReportTemplate clone() {
        try {
            ReportTemplate copy = (ReportTemplate) super.clone();
            copy.columns = new ArrayList<>(this.columns);
            return copy;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }
}

注意 super.clone() 只做字段级复制。如果 columns 不重新创建,新旧对象会共享同一个列表。

四、拷贝构造器写法

另一种常见写法是拷贝构造器:

class ReportTemplate {
    private String type;
    private List<String> columns;

    ReportTemplate(ReportTemplate source) {
        this.type = source.type;
        this.columns = new ArrayList<>(source.columns);
    }
}

使用时:

ReportTemplate copy = new ReportTemplate(template);

这种方式也很清晰,适合字段不太多但需要明确复制规则的对象。

五、复制方法里不要盲目复制所有字段

有些字段不应该复制:

  • 数据库主键;
  • 订单号;
  • 创建时间;
  • 运行时状态;
  • 缓存句柄;
  • 线程、连接、文件句柄。

原型模式不是机械复制全部字段,而是复制“新对象应该继承的初始化状态”。

六、常见误区与追问

Java 实现应把复制语义写进 API,而不是依赖调用者猜测。例子中若 Document 有1个标题、1个可变段落列表和1个模板 ID,copy() 应明确标题复制、段落新建列表、模板 ID 保留或重置。显式实现虽然多写代码,却能在编译期看到新增字段是否进入复制决策。

检查维度判定依据
显式 copy返回具体类型,字段策略可读
Object.clone语法短,异常与浅拷贝语义隐晦

记忆钩子:复制方法是一份字段级契约,不只是创建新对象的技巧。

  • 误区:copy 方法必须复制所有字段。 缓存、主键、锁和外部连接常需要重置或禁止复制。
  • 追问:返回接口类型还是具体类型? 可用协变返回具体类型提升易用性,同时让抽象接口声明复制能力。
  • 误区:新建集合就完成了深拷贝。 若集合元素可变,仍需决定元素是否逐个复制。
  • 追问:继承层次如何维护复制逻辑? 每层处理自身字段并通过受控构造器组合,避免子类漏掉父类状态。
  • 追问:复制后要验证哪些断言? 顶层非同一对象、应独立字段互不影响、应共享字段保持同一引用。

七、加强记忆

Java 实现原型模式可以用 Cloneable,但更推荐显式 copy() 或拷贝构造器。关键不是调用哪个 API,而是把复制规则写清楚:哪些字段复制,哪些字段重建,哪些可变引用要创建新对象。