我们在开发中,有时需要创建一个与现有对象完全相同或非常相似的新对象,最直接的方法是重新创建一个新对象,然后把现有对象的属性值一个个复制过去。
但这样会有很多问题,原型模式(Prototype Pattern)就是针对这个场景的实用解决方案。
举一个简单的例子,我们有一个 User 类,包含用户名和权限列表字段:
// 一个普通的 User 类,没有任何复制能力
class User {
private String name;
private List<String> permissions;
public User(String name, List<String> permissions) {
this.name = name;
this.permissions = permissions;
}
}现在有一个 admin1 用户拥有查看、编辑和删除权限:
List<String> permissions = new ArrayList<>();
permissions.add("view");
permissions.add("edit");
permissions.add("delete");
User admin1 = new User("Admin", permissions);我们想创建一个权限和 admin1 一样,但用户名不同的新用户 admin2。我们可能会这样写代码:
// 需要手动复制 admin1 的权限列表
List<String> admin2Permissions = new ArrayList<>(admin1.getPermissions());
User admin2 = new User("Admin2", admin2Permissions);这种手动复制的方式可以工作,但存在一些明显的问题:
1、代码耦合:创建新对象的代码(客户端代码)与 User 类的内部实现细节耦合在了一起。客户端代码需要知道 User 类的所有属性,才能正确地复制。
2、维护困难:如果 User 类增加了新的属性,那么所有手动复制 User 对象的地方都需要修改,这很容易出错,违反了「开闭原则」。
为了解决这些问题,我们可以使用本文要介绍的原型模式(Prototype Pattern),其核心思想是:不要让调用方来复制对象,而是让对象自己负责复制自己。
我们来看下如何用原型模式来改造 User 类的创建过程。首先,我们定义一个 Cloneable 接口,包含一个 clone 方法用于复制对象:
// 原型接口:约定克隆自身的能力
interface Cloneable<T> {
T clone();
}然后,让 User 类实现这个接口:
class User implements Cloneable<User> {
private String name;
private List<String> permissions;
public User(String name, List<String> permissions) {
this.name = name;
this.permissions = permissions;
}
public List<String> getPermissions() {
return permissions;
}
public void setPermissions(List<String> permissions) {
this.permissions = permissions;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public User clone() {
// 深拷贝:创建新的列表,确保克隆对象和原始对象独立
List<String> clonedPermissions = new ArrayList<>(this.permissions);
return new User(this.name, clonedPermissions);
}
}注意 clone() 方法中创建了一个新的 permissions 列表,而不是直接赋值 this.permissions。这就是所谓的「深拷贝」,即为可变的引用类型字段创建独立副本。如果只是简单赋值(「浅拷贝」),克隆对象和原始对象就会共享同一个列表,修改一个就会影响另一个。
name 字段不需要特殊处理,因为这些语言里字符串都是不可变的,直接赋值就行。
现在,当我们需要一个 User 对象的副本时,只需调用它的 clone() 方法即可:
// 使用 clone() 方法直接复制对象
// 不需要关心 User 类的内部细节
User newUser = admin1.clone();
newUser.setName("newUser");
// 修改 newUser 的数据不会影响 admin1 的数据
newUser.getPermissions().add("publish");通过这种方式,调用方代码不再需要关心 User 类的具体实现了。无论 User 类的字段将来如何修改,只要修改 clone() 方法能够正确地复制自身,调用方代码就无需任何改动,这样就完成了解耦。
点击运行看看效果:
原型模式主要有以下几个关键角色:
- 原型(Prototype):声明一个克隆自身的接口。在这里就是我们自己定义的
Cloneable接口。 - 具体原型(Concrete Prototype):实现克隆接口的类,即上面的
User类。 - 客户端(Client):调用
clone()获取新对象的调用方。