如何手写一个原型模式?Java 代码怎么实现?
简化版
手写原型模式可以定义一个 copy() 方法,让对象自己返回一个新副本。Java 里也可以用 Cloneable 和 clone(),但实际项目更推荐显式写拷贝构造器或 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,而是把复制规则写清楚:哪些字段复制,哪些字段重建,哪些可变引用要创建新对象。