设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
原型模式
> 原型模式是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类。 ![原型设计模式](/media/202203/2022-03-05_2140180.7813128337963677.png) 原型模式(Prototype Pattern,克隆、Clone、Prototype)是用于**创建重复的对象**,同时又能**保证性能**。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。**当直接创建对象的代价比较大时,则采用这种模式**。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。 ## 目的 相比正常创建一个对象,首先创建一个原型,然后克隆它会更节省开销。 ## 介绍 - **意图:** 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 - **主要解决:** 在运行期建立和删除原型。 - **何时使用:** 1. 当一个系统应该独立于它的产品创建,构成和表示时。 2. 当要实例化的类是在**运行时刻指定**时,例如,通过动态装载。 3. 为了避免创建一个与产品类层次平行的工厂类层次时。 4. 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。 - **如何解决:** 利用已有的一个原型对象,快速地生成和原型对象一样的实例。 - **关键代码:** 1. 实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2. 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。 - **应用实例:** 1. 细胞分裂。 2. JAVA 中的 Object clone() 方法。 - **优点:** 1. 性能提高。 2. 逃避构造函数的约束。 - **缺点:** 1. 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2. 必须实现 Cloneable 接口。 - **使用场景:** 1. 资源优化场景。 2. 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3. 性能和安全要求的场景。 4. 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5. 一个对象多个修改者的场景。 6. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7. 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。 - **注意事项:** 与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。 ## 结构 ### 基本实现 ![原型设计模式的结构](/media/202203/2022-03-03_1018530.8509856016282477.png) - 原型 (Prototype) 接口将对克隆方法进行声明。 在绝大多数情况下, 其中只会有一个名为 clone 克隆的方法。 - 具体原型 (Concrete Prototype) 类将实现克隆方法。 除了将原始对象的数据复制到克隆体中之外, 该方法有时还需处理克隆过程中的极端情况, 例如克隆关联对象和梳理递归依赖等等。 - 客户端 (Client) 可以复制实现了原型接口的任何对象。 ### 原型注册表实现 ![原型注册表](/media/202203/2022-03-03_1021260.9582526574046066.png) 原型注册表 (Prototype Registry) 提供了一种**访问常用原型的简单方法**, 其中存储了一系列可供**随时复制的预生成对象**。 最简单的注册表原型是一个 名称 → 原型的哈希表。 但如果需要使用名称以外的条件进行搜索, 你可以创建更加完善的注册表版本。 ### 实例 我们将创建一个抽象类 *Shape* 和扩展了 *Shape* 类的实体类。下一步是定义类 *ShapeCache* ,该类把 shape 对象存储在一个 *Hashtable* 中,并在请求的时候返回它们的克隆。 *PrototypePatternDemo* 类使用 *ShapeCache* 类来获取 *Shape* 对象。 ![原型模式的 UML 图](/media/202203/2022-03-03_1049250.6547430537030166.png) ## 伪代码 在本例中, **原型**模式能让你生成完全相同的几何对象副本,同时无需代码与对象所属类耦合。 ![原型模式示例的结构](/media/202203/2022-03-03_1024390.6089532351116446.png) 克隆一系列位于同一类层次结构中的对象。 所有形状类都遵循同一个提供克隆方法的接口。**在复制自身成员变量值到结果对象前**,**子类可调用其父类的克隆方法**。 ``` // 基础原型。 abstract class Shape is field X: int field Y: int field color: string // 常规构造函数。 constructor Shape() is // ... // 原型构造函数。使用已有对象的数值来初始化一个新对象。 constructor Shape(source: Shape) is this() this.X = source.X this.Y = source.Y this.color = source.color // clone(克隆)操作会返回一个形状子类。 abstract method clone():Shape // 具体原型。克隆方法会创建一个新对象并将其传递给构造函数。直到构造函数运 // 行完成前,它都拥有指向新克隆对象的引用。因此,任何人都无法访问未完全生 // 成的克隆对象。这可以保持克隆结果的一致。 class Rectangle extends Shape is field width: int field height: int constructor Rectangle(source: Rectangle) is // 需要调用父构造函数来复制父类中定义的私有成员变量。 super(source) this.width = source.width this.height = source.height method clone():Shape is return new Rectangle(this) class Circle extends Shape is field radius: int constructor Circle(source: Circle) is super(source) this.radius = source.radius method clone():Shape is return new Circle(this) // 客户端代码中的某个位置。 class Application is field shapes: array of Shape constructor Application() is Circle circle = new Circle() circle.X = 10 circle.Y = 10 circle.radius = 20 shapes.add(circle) Circle anotherCircle = circle.clone() shapes.add(anotherCircle) // 变量 `anotherCircle(另一个圆)` 与 `circle(圆)` 对象的内 // 容完全一样。 Rectangle rectangle = new Rectangle() rectangle.width = 10 rectangle.height = 20 shapes.add(rectangle) method businessLogic() is // 原型是很强大的东西,因为它能在不知晓对象类型的情况下生成一个与 // 其完全相同的复制品。 Array shapesCopy = new Array of Shapes. // 例如,我们不知晓形状数组中元素的具体类型,只知道它们都是形状。 // 但在多态机制的帮助下,当我们在某个形状上调用 `clone(克隆)` // 方法时,程序会检查其所属的类并调用其中所定义的克隆方法。这样, // 我们将获得一个正确的复制品,而不是一组简单的形状对象。 foreach (s in shapes) do shapesCopy.add(s.clone()) // `shapesCopy(形状副本)` 数组中包含 `shape(形状)` 数组所有 // 子元素的复制品。 ``` ## 原型模式适合应用场景 - 如果你需要复制一些对象,同时又希望代码独立于这些对象所属的具体类,可以使用原型模式。 这一点考量通常出现在代码需要处理第三方代码通过接口传递过来的对象时。即使不考虑代码耦合的情况,你的代码也不能依赖这些对象所属的具体类,因为你不知道它们的具体信息。 原型模式为客户端代码提供一个通用接口,客户端代码可通过这一接口与所有实现了克隆的对象进行交互,它也使得客户端代码与其所克隆的对象具体类独立开来。 - 如果子类的区别仅在于其对象的初始化方式,那么你可以使用该模式来减少子类的数量。别人创建这些子类的目的可能是为了创建特定类型的对象。 在原型模式中,你可以使用一系列预生成的、各种类型的对象作为原型。 客户端不必根据需求对子类进行实例化,只需找到合适的原型并对其进行克隆即可。 ## 实现方式 - 创建原型接口, 并在其中声明克隆方法。 如果你已有类层次结构, 则只需在其所有类中添加该方法即可。 - 原型类必须另行定义一个以该类对象为参数的构造函数。 构造函数必须复制参数对象中的所有成员变量值到新建实体中。 如果你需要修改子类, 则必须调用父类构造函数, 让父类复制其私有成员变量值。 如果编程语言不支持方法重载, 那么你可能需要定义一个特殊方法来复制对象数据。 在构造函数中进行此类处理比较方便, 因为它在调用 new 运算符后会马上返回结果对象。 - 克隆方法通常只有一行代码: 使用 new 运算符调用原型版本的构造函数。 注意, 每个类都必须显式重写克隆方法并使用自身类名调用 new 运算符。 否则, 克隆方法可能会生成父类的对象。 - 你还可以创建一个中心化原型注册表, 用于存储常用原型。 你可以新建一个工厂类来实现注册表, 或者在原型基类中添加一个获取原型的静态方法。 该方法必须能够根据客户端代码设定的条件进行搜索。 搜索条件可以是简单的字符串, 或者是一组复杂的搜索参数。 找到合适的原型后, 注册表应对原型进行克隆, 并将复制生成的对象返回给客户端。 最后还要将对子类构造函数的直接调用替换为对原型注册表工厂方法的调用。 ## 优点 - 你可以克隆对象, 而无需与它们所属的具体类相耦合。 - 你可以克隆预生成原型, 避免反复运行初始化代码。 - 你可以更方便地生成复杂对象。 - 你可以用继承以外的方式来处理复杂对象的不同配置。 ## 缺点 - 克隆包含循环引用的复杂对象可能会非常麻烦。 ## 与其他模式的关系 - 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。 - 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。 - 原型可用于**保存命令模式的历史记录**。 - 大量使用组合模式和装饰模式的设计通常可从对于原型的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。 - 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。 - 有时候原型可以作为备忘录模式的一个简化版本, 其条件是你需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建。 - 抽象工厂、 生成器和原型都可以用单例模式来实现。 ## 示例代码 ### TS ```ts /** * The example class that has cloning ability. We'll see how the values of field * with different types will be cloned. */ class Prototype { public primitive: any; public component: object; public circularReference: ComponentWithBackReference; public clone(): this { const clone = Object.create(this); clone.component = Object.create(this.component); // Cloning an object that has a nested object with backreference // requires special treatment. After the cloning is completed, the // nested object should point to the cloned object, instead of the // original object. Spread operator can be handy for this case. clone.circularReference = { ...this.circularReference, prototype: { ...this }, }; return clone; } } class ComponentWithBackReference { public prototype; constructor(prototype: Prototype) { this.prototype = prototype; } } /** * The client code. */ function clientCode() { const p1 = new Prototype(); p1.primitive = 245; p1.component = new Date(); p1.circularReference = new ComponentWithBackReference(p1); const p2 = p1.clone(); if (p1.primitive === p2.primitive) { console.log('Primitive field values have been carried over to a clone. Yay!'); } else { console.log('Primitive field values have not been copied. Booo!'); } if (p1.component === p2.component) { console.log('Simple component has not been cloned. Booo!'); } else { console.log('Simple component has been cloned. Yay!'); } if (p1.circularReference === p2.circularReference) { console.log('Component with back reference has not been cloned. Booo!'); } else { console.log('Component with back reference has been cloned. Yay!'); } if (p1.circularReference.prototype === p2.circularReference.prototype) { console.log('Component with back reference is linked to original object. Booo!'); } else { console.log('Component with back reference is linked to the clone. Yay!'); } } clientCode(); ``` 输出: ```txt Primitive field values have been carried over to a clone. Yay! Simple component has been cloned. Yay! Component with back reference has been cloned. Yay! Component with back reference is linked to the clone. Yay! ```
追风者
2022年3月20日 22:24
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码