什么是原型模式(Prototype Pattern)

原型模式指定创建对象的种类,通过拷贝原型来创建新的对象。它允许你复制现有对象,而无需使你的代码依赖于它们的类。
想象一下你在玩一个策略游戏,需要快速创建大量相似的单位(比如步兵)。与其每次都从头构建(分配内存、设置初始属性、加载资源等),不如先创建一个“原型”步兵单位,然后当你需要新步兵时,直接“克隆”这个原型。克隆出来的单位与原型具有相同的初始状态,之后可以根据需要进行微调(比如设置不同的位置)。
原型模式的核心在于通过复制一个现有实例来创建新的实例,而不是通过 new 操作符和构造函数。

核心思想

  1. 定义原型接口/抽象类 (Prototype): 定义一个接口或抽象类,声明一个用于克隆自身的方法,通常命名为 clone() 或 copy()。
  1. 创建具体原型 (ConcretePrototype): 实现 Prototype 接口/继承抽象类。这个类实现了克隆方法。克隆操作需要复制当前对象的状态(成员变量)到新的对象中。
  1. 客户端 (Client): 客户端需要新对象时,不是直接实例化具体类,而是获取一个原型对象,并调用其克隆方法来获得一个副本。客户端通常需要将返回的对象转换为所需的具体类型(如果原型接口返回的是通用类型)。
  1. (可选) 原型管理器/注册表 (Prototype Manager/Registry): 有时会有一个管理器类,负责存储和检索原型对象。客户端可以向管理器请求特定名称或类型的原型,然后克隆它。
关键点:浅拷贝 (Shallow Copy) vs 深拷贝 (Deep Copy)
这是理解和正确使用原型模式最重要的部分:
  • 浅拷贝 (Shallow Copy): 只复制对象本身和其中的基本数据类型成员。如果对象包含对其他对象的引用,则只复制引用地址,而不复制引用所指向的对象。这意味着克隆对象和原始对象将共享这些引用的对象。修改共享对象会影响到原始对象和所有克隆对象。
  • 深拷贝 (Deep Copy): 复制对象本身及其所有基本数据类型成员,并且递归地复制其引用的所有对象。克隆对象和原始对象是完全独立的,修改克隆对象引用的对象不会影响原始对象,反之亦然。
选择哪种拷贝方式取决于具体需求。如果不注意区分,可能会因意外共享对象而导致难以调试的 Bug。

优点

  1. 性能提升 (Performance Improvement): 如果对象的创建过程非常昂贵(例如,需要访问数据库、进行复杂计算、加载大量资源),通过克隆现有对象可以显著提高性能。
  1. 简化对象创建 (Simplified Object Creation): 隐藏了对象创建的复杂性。客户端只需要调用 clone() 方法即可。
  1. 增加/删除产品方便: 可以通过动态添加或删除原型注册表中的原型,在运行时改变可以创建的对象种类。
  1. 用原型代替继承: 可以通过配置不同状态的原型对象,然后克隆它们,来代替创建大量的子类。例如,不同配置的游戏角色可以通过克隆预设的原型来创建。
  1. 灵活性: 客户端可以克隆一个对象而无需知道其具体类型,只需要知道它实现了原型接口。

可能的缺点

  1. 克隆复杂对象困难: 对于包含复杂引用关系(特别是循环引用)的对象,实现深拷贝可能非常复杂且容易出错。
  1. 需要为每个类实现克隆方法: 每个需要被克隆的具体原型类都必须实现克隆逻辑。
  1. 对克隆的类的限制: 有些类可能因为包含不能被轻易复制的资源(如线程、文件句柄)而不适合克隆。
  1. 浅拷贝陷阱: 如果开发者没有意识到浅拷贝的影响,可能会导致意外的数据共享和错误。

使用场景

  1. 当对象的创建成本很高(时间或资源消耗大)时。
  1. 当需要创建的对象与现有对象仅有状态上的少量差异时。
  1. 当一个系统需要独立于其产品的创建、构成和表示时。
  1. 当希望动态地加载类或者在运行时指定要创建的对象类型时(通过原型注册表)。
  1. 需要大量相似对象的场景,如游戏中的粒子效果、大量敌人、UI 元素的复制。
  1. 配置对象的复制: 复制复杂的配置对象,然后进行微调。
  1. 缓存实现: 从缓存中获取对象时,可能返回一个克隆版本,以防止客户端修改缓存中的原始对象。
  1. Android 中的场景:
      • Kotlin 的 data class 的 copy() 方法: 这是 Kotlin 语言内置的原型模式实现。data class 自动生成 copy() 方法,允许你方便地创建一个对象的副本,并可以选择性地修改某些属性。请注意:data class 的 copy() 默认执行的是浅拷贝! 如果 data class 包含可变对象引用,需要特别小心。
      • Bundle 类: Android 中的 Bundle 实现了 Cloneable 接口,并提供了 clone() 方法(虽然是 protected,但可以通过 putBundle / getBundle 实现类似效果),用于复制包含键值对的数据集。
      • Intent 类: Intent 也实现了 Cloneable,可以被克隆。
      • 自定义对象的快照: 如果你需要为一个复杂的可变对象创建某个时间点的快照,可以实现克隆接口。
      • 预设配置: 在应用中定义一些预设的配置对象(如图表样式、网络请求模板),当需要基于这些预设创建新配置时,可以克隆原型。

UML 图

下面是基于一个简单的“形状 (Shape)”示例的原型模式 UML 图:
  • Shape (Abstract Prototype): 定义了抽象的 clone() 方法。可能包含一些所有形状共有的属性(如 id, color)和一个拷贝构造函数(方便子类实现克隆)。
  • Circle, Rectangle (ConcretePrototype): 继承 Shape,实现了 clone() 方法,通常通过调用自己的拷贝构造函数来完成。
  • ShapeRegistry (Prototype Manager): (可选) 存储预先创建和配置好的原型对象,客户端可以通过 key 来获取并克隆它们。
  • Client: 从注册表获取原型或直接持有原型对象,调用 clone() 来创建新实例。

代码示例

在这个例子中:
  • Shape 是抽象原型,Circle 和 Rectangle 是具体原型。
  • 我们显式地实现了 clone() (浅拷贝 style) 和 cloneDeep() (深拷贝 style) 来演示区别。
  • 客户端代码通过调用 clone() 和 cloneDeep() 创建副本,并修改副本的 style 属性,观察对原始对象和其他副本的影响,清晰地展示了浅拷贝和深拷贝的区别。
  • 最后还用 Kotlin 的 data class 展示了其 copy() 方法默认是浅拷贝的行为。

总结

  • 原型模式是一种通过复制现有对象来创建新对象的创建型模式。
  • 它的主要优点在于性能(当对象创建昂贵时)和简化创建过程
  • 理解并正确处理浅拷贝和深拷贝是成功运用此模式的关键,否则可能导致难以发现的共享状态问题。
  • Kotlin 的 data class 提供了便捷的 copy() 方法,是原型模式的一种常见实现,但需注意其默认的浅拷贝行为
💡
记住它的核心:要新的?别造了,拿个旧的(原型)复印(clone)一份!但小心复印件和原件是不是还连着(浅拷贝 vs 深拷贝)!
Gradle系列——Transform与TransformAction设计模式——行为型-解释器模式
Loading...