JavaScript设计模式-创建型-原型模式

2021-05-18 JavaScript设计模式 阅读 724 次

概要

原型模式是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类。

原型模式不单是一种设计模式,也被称为一种编程泛型。

如果我们想要创建一个对象,一种方法是先指定它的类型,然后通过类来创建这个对象。原型模式选择了另外一种方式,我们不再关心对象的具体类型,而是找到一个对象,然后通过克隆来创建一个一模一样的对象。

JavaScript设计模式-创建型-原型模式-黄继鹏博客

问题

假设我们有一个Plane 飞机对象,我们希望复制出一个一模一样的Plane 飞机对象。我们该如何实现呢?

首先我们要新建一个和Plane 飞机属于相同类的对象,然后, 遍历Plane 飞机对象的所有成员变量, 并将成员变量值复制到新对象中。

这样直接复制存在一些问题,并不是所有的对象都能直接这样复制,因为有些对象本身拥有内部属性和方法,他们在对象原型链中,通过对象本身是不可见的。

JavaScript设计模式-创建型-原型模式-黄继鹏博客

直接复制还存在一个问题,我们必须知道对象所属的类才能创建复制品,我们在复制的过程中还必须知道,被复制对象的所有依赖或者依赖的类。

即使我们可以接受额外的依赖性,但还有个问题是我们知道对象所有实现的接口或方法,但不知道这些接口或方法其所属的具体类,比如说这些接口方法的调用参数,以及参数对逻辑处理影响的具体属性。

解决方案

原型模式将克隆的过程委派给被克隆的实际对象中,原型模式为所有支持克隆的对象提供了一个通用接口,该接口能够让对象克隆自己本身,同时又无需将代码和对象所属类耦合。一般情况下, 这样的接口中仅包含一个 克隆方法。

克隆接口会创建一个当前类的对象,然后将原始对象所有的成员变量值复制到新建的类中,也可以复制私有成员变量。

支持克隆的对象我们称之为为原型。 当你的对象有几十个成员变量和几百种类型时, 对它进行克隆有时候甚至可以代替子类的构造。

JavaScript设计模式-创建型-原型模式-黄继鹏博客

原型模式结构图

JavaScript设计模式-创建型-原型模式-黄继鹏博客

  1. 抽象原型类 (Prototype) 声明clone方法。 通常情况下, 只会有一个名为 clone克隆的方法,这里举例是抽象原型基类,其实在简单的场景下可以不用抽象原型基类,直接使用具体原型类就可以了。

  2. 具体原型类 (Concrete Prototype) 在这里实现扩展克隆方法。 除了将原始对象的数据复制到克隆体中之外, 该方法有时还需处理克隆过程中的极端情况, 例如克隆关联对象和梳理递归依赖等等。

  3. 客户端 (Client)请求, 可以复制实现了原型接口的任何对象。

举例

假设我们已经生成了几个几何体,比如Rectangle 矩形Circle 圆,我们如何复制出与两个几何体一模一样的副本呢?并且同时无需代码与对象所属类耦合?

JavaScript设计模式-创建型-原型模式-黄继鹏博客

我们需要将形状类都遵循同一个提供克隆的接口clone,通过实例化后的对象支持调用clone接口来进行克隆。

实例化后的对象我们称之为为原型,在执行clone克隆接口进行复制时,我们将把原型对象自身成员变量属性传递给具体原型类(当前类)重新实例化进行复制。

/**
 * 基础几何抽象原型类
 */
class Shape {
  // 常规构造函数。
  constructor(source = {}) {
    this.X = source.X || 0
    this.Y = source.Y || 0
    this.color = source.color || 'white'
  }

  clone() {
    throw new Error('clone 方法没有实现!')
  }
}

/**
 * 具体原型。克隆方法会创建一个新对象并将其传递给构造函数。
 */
class Rectangle extends Shape {
  constructor(source = {}) {
    // 需要调用父构造函数来复制父类中定义的私有成员变量。
    super(source)
    this.width = source.width || 0
    this.height = source.height || 0
  }

  clone() {
    return new Rectangle(this)
  }
}

class Circle extends Shape {
  constructor(source = {}) {
    super(source)
    this.radius = source.radius || 0
  }

  clone() {
    return new Circle(this)
  }
}

function copyShapes() {
  const rectangle = new Rectangle({ width: 100, height: 50 })

  const anotherRectangle = rectangle.clone()
  anotherRectangle.color = 'yellow'

  const circle = new Circle()
  circle.X = 10
  circle.Y = 10
  circle.radius = 20

  // 变量 `anotherCircle(另一个圆)`与 `circle(圆)`对象的内
  // 容完全一样。
  const anotherCircle = circle.clone()

  console.log(rectangle)
  console.log(anotherRectangle)
  console.log(circle)
  console.log(anotherCircle)
}

copyShapes()

output

{
  "X": 0,
  "Y": 0,
  "color": "white",
  "width": 100,
  "height": 50
}
{
  "X": 0,
  "Y": 0,
  "color": "yellow",
  "width": 100,
  "height": 50
}
{
  "X": 10,
  "Y": 10,
  "color": "white",
  "radius": 20
}
{
  "X": 10,
  "Y": 10,
  "color": "white",
  "radius": 20
}

总结

如果我们需要复制一些复杂对象,同时希望在创建复制对象的代码中做到与被复制对象所属依赖或者所属类之间做到解耦,或者说我们想简单明了的复制某些复杂对象时,可以使用原型模式。

  • 优点

    • 原型模式可以克隆对象, 而不需要与它所属的具体类相耦合。
    • 原型模式可以克隆预生成原型, 避免反复运行初始化代码。
    • 原型模式可以更方便地生成复杂对象。
    • 原型模式可以用继承以外的方式来处理复杂对象的不同配置。
  • 缺点

    • 克隆包含循环引用的复杂对象可能会非常麻烦。
0条评论
...