圣诞大减价开始了!

原型

也被称为:克隆

意图

原型是一种创造性的设计模式,允许您复制现有对象,而无需使您的代码依赖于它们的类。

原型设计模式

问题

假设您有一个对象,并且您想要创建它的精确副本。你会怎么做?首先,您必须创建一个相同类的新对象。然后,您必须遍历原始对象的所有字段,并将它们的值复制到新对象。

好了!但有一个问题。并不是所有对象都可以这样复制,因为对象的一些字段可能是私有的,从对象本身外部不可见。

“从外部”复制东西会出什么问题?“宽度=

“从外部”复制一个对象不是总是可能的。

这种直接方法还有一个问题。因为必须知道对象的类才能创建副本,所以代码依赖于该类。如果额外的依赖没有吓到你,还有另一个问题。有时,您只知道对象遵循的接口,而不知道它的具体类,例如,当方法中的参数接受遵循某个接口的任何对象时。

解决方案

Prototype模式将克隆过程委托给被克隆的实际对象。该模式为所有支持克隆的对象声明了一个公共接口。该接口允许您克隆对象,而无需将代码与该对象的类耦合。通常,这样的接口只包含一个克隆方法。

实施克隆方法在所有类中都非常相似。该方法创建当前类的对象,并将旧对象的所有字段值转移到新对象中。您甚至可以复制私有字段,因为大多数编程语言允许对象访问属于同一类的其他对象的私有字段。

支持克隆的对象称为原型.当对象具有数十个字段和数百个可能的配置时,克隆它们可以作为子类化的替代方法。

预先构建的原型

预先构建的原型可以作为子类化的替代方案。

下面是它的工作原理:创建一组对象,以各种方式配置。当您需要一个类似于您已经配置的对象时,您只需克隆一个原型,而不是从头构造一个新对象。

真实的模拟

在现实生活中,原型用于在开始大规模生产产品之前进行各种测试。然而,在这种情况下,原型并不参与任何实际的生产,而是扮演一个被动的角色。

细胞分裂

细胞的分裂

由于工业原型并不真正复制自己,一个更接近这种模式的类比是有丝分裂细胞分裂的过程(生物学,还记得吗?)有丝分裂后,形成一对相同的细胞。原始单元格充当原型,并在创建副本时发挥积极作用。

结构

基本实现

原型设计模式的结构 原型设计模式的结构
  1. 原型接口声明克隆方法。在大多数情况下,它是单一的克隆方法。

  2. 具体的原型类实现克隆方法。除了将原始对象的数据复制到克隆对象之外,此方法还可以处理克隆过程中与克隆链接对象相关的一些边缘情况,解开递归依赖关系等。

  3. 客户端可以生成遵循原型接口的任何对象的副本。

原型注册表实现

原型注册表 原型注册表
  1. 原型注册表提供访问常用原型的简便方法。它存储了一组准备复制的预构建对象。最简单的原型注册表是名称→原型散列映射。但是,如果您需要比简单名称更好的搜索条件,则可以构建一个更加健壮的注册中心版本。

伪代码

在本例中,原型Pattern允许您生成几何对象的精确副本,而无需将代码与它们的类耦合。

Prototype模式的结构示例

克隆属于类层次结构的一组对象。

所有形状类都遵循相同的接口,该接口提供了克隆方法。子类可以在将自己的字段值复制到结果对象之前调用父类的克隆方法。

//基本原型。抽象类Shape is field X: int field Y: int field color: string //正则构造函数。构造函数Shape()是//…//原型构造函数。用现有对象的值初始化一个新对象。构造函数Shape(来源:Shape)是this() this。X =源。X。Y =源。Y。Color = source。color //克隆操作返回一个Shape子类。抽象方法clone():Shape //具体的原型。 The cloning method creates a new object // in one go by calling the constructor of the current class and // passing the current object as the constructor's argument. // Performing all the actual copying in the constructor helps to // keep the result consistent: the constructor will not return a // result until the new object is fully built; thus, no object // can have a reference to a partially-built clone. class Rectangle extends Shape is field width: int field height: int constructor Rectangle(source: Rectangle) is // A parent constructor call is needed to copy private // fields defined in the parent class. 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) // Somewhere in the client code. 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) // The `anotherCircle` variable contains an exact copy // of the `circle` object. Rectangle rectangle = new Rectangle() rectangle.width = 10 rectangle.height = 20 shapes.add(rectangle) method businessLogic() is // Prototype rocks because it lets you produce a copy of // an object without knowing anything about its type. Array shapesCopy = new Array of Shapes. // For instance, we don't know the exact elements in the // shapes array. All we know is that they are all // shapes. But thanks to polymorphism, when we call the // `clone` method on a shape the program checks its real // class and runs the appropriate clone method defined // in that class. That's why we get proper clones // instead of a set of simple Shape objects. foreach (s in shapes) do shapesCopy.add(s.clone()) // The `shapesCopy` array contains exact copies of the // `shape` array's children.

适用性

当您的代码不应该依赖于需要复制的对象的具体类时,请使用Prototype模式。

当您的代码使用通过某些接口从第三方代码传递给您的对象时,这种情况经常发生。这些对象的具体类是未知的,即使您想依赖它们,也不能依赖它们。

Prototype模式为客户端代码提供了用于处理所有支持克隆的对象的通用接口。该接口使客户端代码独立于它克隆的对象的具体类。

当您想要减少只在初始化各自对象的方式上有所不同的子类的数量时,请使用该模式。

假设您有一个复杂的类,在使用它之前需要费力的配置。有几种常见的方法来配置这个类,这些代码分散在你的应用程序中。为了减少重复,你创建了几个子类,并将每个常见的配置代码放入它们的构造函数中。你解决了复制问题,但现在你有很多虚拟子类。

Prototype模式允许您使用一组以各种方式配置的预构建对象作为原型。客户端可以简单地寻找一个合适的原型并克隆它,而不是实例化一个匹配某些配置的子类。

如何实施

  1. 创建原型接口并声明克隆方法在里面。或者只是将该方法添加到现有类层次结构中的所有类(如果有的话)。

  2. 原型类必须定义可选构造函数,该构造函数接受该类的对象作为参数。构造函数必须将类中定义的所有字段的值从传递的对象复制到新创建的实例中。如果要更改子类,则必须调用父构造函数以让父类处理其私有字段的克隆。

    如果你的编程语言不支持方法重载,你就不能创建一个单独的“原型”构造函数。因此,将对象的数据复制到新创建的克隆中必须在克隆方法。不过,在常规构造函数中使用此代码更安全,因为在调用后返回的结果对象已完全配置操作符。

  3. 克隆方法通常只包含一行:运行操作符与构造函数的原型版本。注意,每个类必须显式重写克隆方法,并使用自己的类名和克隆方法操作符。否则,克隆方法可能产生父类的对象。

  4. 可选地,创建一个集中的原型注册表来存储经常使用的原型目录。

    您可以将注册表实现为一个新的工厂类,或者将其放在基原型类中,并使用静态方法获取原型。该方法应该基于客户端代码传递给该方法的搜索条件来搜索原型。条件可能是一个简单的字符串标签,也可能是一组复杂的搜索参数。找到合适的原型后,注册中心应该克隆它并将副本返回给客户端。

    最后,将对子类构造函数的直接调用替换为对原型注册表的工厂方法的调用。

利与弊

  • 你可以克隆对象而不与它们的具体类耦合。
  • 您可以摆脱重复的初始化代码,转而克隆预先构建的原型。
  • 您可以更方便地生成复杂的对象。
  • 在处理复杂对象的配置预设时,您可以获得继承的替代方案。
  • 克隆具有循环引用的复杂对象可能非常棘手。

与其他模式的关系

  • 许多设计从使用开始工厂方法(更简单,更可通过子类定制),并朝着抽象工厂,原型,或构建器(更灵活,但更复杂)。

  • 抽象工厂类通常基于一组工厂方法,但你也可以用原型来组合这些类上的方法。

  • 原型可以帮助您在需要保存副本的时候命令进入历史。

  • 大量使用的设计复合而且装饰能经常受益于使用吗原型.应用该模式可以克隆复杂的结构,而不是从头重新构造它们。

  • 原型不是基于继承的,所以它没有缺点。另一方面,原型需要对克隆对象进行复杂的初始化。工厂方法基于继承,但不需要初始化步骤。

  • 有时原型可以有更简单的替代方案吗纪念品.如果希望在历史记录中存储其状态的对象相当简单,并且没有到外部资源的链接,或者这些链接很容易重新建立,则此方法有效。

  • 抽象工厂,建筑商而且原型可以全部实现为单例

代码示例

c#原型c++的原型Go中的原型Java原型PHP原型Python原型Ruby原型Rust中的原型Swift原型TypeScript中的原型

Baidu
map