原型模式
我们在开发中,有时需要创建一个与现有对象完全相同或非常相似的新对象,最直接的方法是重新创建一个新对象,然后把现有对象的属性值一个个复制过去。
但这样会有很多问题,原型模式(Prototype Pattern)就是针对这个场景的实用解决方案。
举一个简单的例子,我们有一个 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() {
// 一定要创建一个新的 permissions 列表复制所有元素
// 这是为了实现「深拷贝」,确保克隆对象和原始对象的权限列表是独立的
// 否则原始对象和克隆对象之间可能存在共享的引用,导致意外的修改
List<String> clonedPermissions = new ArrayList<>(this.permissions);
return new User(this.name, clonedPermissions);
}
}
现在,当我们需要一个 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()
获取新对象的调用方。
原型管理器
在某些场景下,我们可能需要管理一系列可供克隆的原型对象。这时可以引入一个原型管理器(Prototype Manager)或者叫原型注册表(Prototype Registry)。
原型管理器是一个集中的地方,用于存储和检索原型对象。客户端不再直接与原型对象打交道,而是通过一个唯一的标识(如字符串名称)向管理器请求克隆对象。
还是以 User
类为例,我们创建一个原型管理器:
class PrototypeManager {
private Map<String, User> prototypes = new HashMap<>();
public void addPrototype(String key, User user) {
prototypes.put(key, user);
}
public User getPrototype(String key) {
User prototype = prototypes.get(key);
if (prototype != null) {
return prototype.clone();
}
return null;
}
}
向原型管理器注册一些预设的用户类型:
// 1. 创建原型管理器
PrototypeManager manager = new PrototypeManager();
// 2. 创建并注册原型对象
User readonly = new User("readonly", Arrays.asList("view"));
User admin = new User("admin", Arrays.asList("view", "edit", "delete"));
User superAdmin = new User("superAdmin", Arrays.asList("view", "edit", "delete", "publish"));
manager.addPrototype("readonly", readonly);
manager.addPrototype("admin", admin);
manager.addPrototype("superAdmin", superAdmin);
当需要创建一个新用户时,只需向原型管理器请求:
// 创建普通用户
User user1 = manager.getPrototype("readonly");
user1.setName("John");
// 创建管理员用户
User user2 = manager.getPrototype("admin");
user2.setName("Tom");
// 创建超级管理员用户
User user3 = manager.getPrototype("superAdmin");
user3.setName("Admin");
原型管理器在需要动态配置或管理多种原型对象的系统中非常有用,它将对象的创建和使用分离,提高了系统的灵活性和可扩展性。
总结
原型模式的主要优势:
1、解耦:客户端代码与具体的类实现解耦,客户端只需要知道如何克隆对象,而不需要知道对象的创建细节。
2、简化对象创建:对于复杂的对象,通过克隆一个现有对象来创建新对象,比通过构造函数从头创建要简单得多。
3、性能提升:如果对象的创建过程非常耗时(例如,需要访问数据库或网络),那么通过克隆来创建对象可以显著提高性能。
原型模式的主要缺点:
为每个类正确地实现深拷贝的 clone()
方法可能很复杂,尤其是当对象包含复杂的嵌套结构或循环引用时,需要小心处理。
总的来说,当你需要创建的对象与一个现有对象非常相似,或者对象的创建成本很高时,原型模式是一个非常值得考虑的设计模式。