什么是原型模式(Prototype Pattern)
原型模式指定创建对象的种类,通过拷贝原型来创建新的对象。它允许你复制现有对象,而无需使你的代码依赖于它们的类。
想象一下你在玩一个策略游戏,需要快速创建大量相似的单位(比如步兵)。与其每次都从头构建(分配内存、设置初始属性、加载资源等),不如先创建一个“原型”步兵单位,然后当你需要新步兵时,直接“克隆”这个原型。克隆出来的单位与原型具有相同的初始状态,之后可以根据需要进行微调(比如设置不同的位置)。
原型模式的核心在于通过复制一个现有实例来创建新的实例,而不是通过 new 操作符和构造函数。
核心思想
- 定义原型接口/抽象类 (Prototype): 定义一个接口或抽象类,声明一个用于克隆自身的方法,通常命名为 clone() 或 copy()。
- 创建具体原型 (ConcretePrototype): 实现 Prototype 接口/继承抽象类。这个类实现了克隆方法。克隆操作需要复制当前对象的状态(成员变量)到新的对象中。
- 客户端 (Client): 客户端需要新对象时,不是直接实例化具体类,而是获取一个原型对象,并调用其克隆方法来获得一个副本。客户端通常需要将返回的对象转换为所需的具体类型(如果原型接口返回的是通用类型)。
- (可选) 原型管理器/注册表 (Prototype Manager/Registry): 有时会有一个管理器类,负责存储和检索原型对象。客户端可以向管理器请求特定名称或类型的原型,然后克隆它。
关键点:浅拷贝 (Shallow Copy) vs 深拷贝 (Deep Copy)
这是理解和正确使用原型模式最重要的部分:
- 浅拷贝 (Shallow Copy): 只复制对象本身和其中的基本数据类型成员。如果对象包含对其他对象的引用,则只复制引用地址,而不复制引用所指向的对象。这意味着克隆对象和原始对象将共享这些引用的对象。修改共享对象会影响到原始对象和所有克隆对象。
- 深拷贝 (Deep Copy): 复制对象本身及其所有基本数据类型成员,并且递归地复制其引用的所有对象。克隆对象和原始对象是完全独立的,修改克隆对象引用的对象不会影响原始对象,反之亦然。
选择哪种拷贝方式取决于具体需求。如果不注意区分,可能会因意外共享对象而导致难以调试的 Bug。
优点
- 性能提升 (Performance Improvement): 如果对象的创建过程非常昂贵(例如,需要访问数据库、进行复杂计算、加载大量资源),通过克隆现有对象可以显著提高性能。
- 简化对象创建 (Simplified Object Creation): 隐藏了对象创建的复杂性。客户端只需要调用 clone() 方法即可。
- 增加/删除产品方便: 可以通过动态添加或删除原型注册表中的原型,在运行时改变可以创建的对象种类。
- 用原型代替继承: 可以通过配置不同状态的原型对象,然后克隆它们,来代替创建大量的子类。例如,不同配置的游戏角色可以通过克隆预设的原型来创建。
- 灵活性: 客户端可以克隆一个对象而无需知道其具体类型,只需要知道它实现了原型接口。
可能的缺点
- 克隆复杂对象困难: 对于包含复杂引用关系(特别是循环引用)的对象,实现深拷贝可能非常复杂且容易出错。
- 需要为每个类实现克隆方法: 每个需要被克隆的具体原型类都必须实现克隆逻辑。
- 对克隆的类的限制: 有些类可能因为包含不能被轻易复制的资源(如线程、文件句柄)而不适合克隆。
- 浅拷贝陷阱: 如果开发者没有意识到浅拷贝的影响,可能会导致意外的数据共享和错误。
使用场景
- 当对象的创建成本很高(时间或资源消耗大)时。
- 当需要创建的对象与现有对象仅有状态上的少量差异时。
- 当一个系统需要独立于其产品的创建、构成和表示时。
- 当希望动态地加载类或者在运行时指定要创建的对象类型时(通过原型注册表)。
- 需要大量相似对象的场景,如游戏中的粒子效果、大量敌人、UI 元素的复制。
- 配置对象的复制: 复制复杂的配置对象,然后进行微调。
- 缓存实现: 从缓存中获取对象时,可能返回一个克隆版本,以防止客户端修改缓存中的原始对象。
- 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 深拷贝)!
- Author:CoderWdd
- URL:https://www.wuinsights.top//article/66d03cb6-6167-4181-86f4-d6d8169239f8
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts