原型模式

我们在开发中,有时需要创建一个与现有对象完全相同或非常相似的新对象,最直接的方法是重新创建一个新对象,然后把现有对象的属性值一个个复制过去。

但这样会有很多问题,原型模式(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() 获取新对象的调用方。