天天看点

经典创建型设计模式:原型模式

作者:风趣生命的起源

意图

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

经典创建型设计模式:原型模式

问题

假设您有一个对象,并且您想创建它的一个完全相同的副本。您该如何做呢?首先,您需要创建一个相同类的新对象。然后,您需要遍历原始对象的所有字段,并将它们的值复制到新对象中。

不错!但是有一个问题。并非所有对象都可以通过这种方式进行复制,因为其中一些字段可能是私有的,无法从对象外部访问。

经典创建型设计模式:原型模式

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

直接方法还有一个问题。由于您需要知道对象的类来创建副本,因此您的代码会依赖于该类。如果额外的依赖关系不让您担心,那还有一个问题。有时您只知道对象遵循的接口,而不知道具体的类,例如,当方法中的参数接受遵循某个接口的任何对象时。

解决方法

原型模式将克隆过程委托给实际被克隆的对象。该模式为所有支持克隆的对象声明了一个通用接口。这个接口允许您在不将代码耦合到对象的具体类的情况下进行克隆。通常,这样的接口只包含一个克隆方法。

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

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

经典创建型设计模式:原型模式

预构建的原型可以作为继承的一种替代方式。

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

真实世界类比

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

经典创建型设计模式:原型模式

细胞的分裂。

由于工业原型实际上并不自我复制,与该模式更接近的类比是有丝分裂细胞分裂的过程(还记得生物学吗?)。在有丝分裂之后,形成一对完全相同的细胞。原始细胞充当原型,并在创建副本时扮演主动的角色。

结构

基本实现方式

经典创建型设计模式:原型模式

1、原型接口声明克隆方法。在大多数情况下,它只有一个克隆方法。

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

3、客户端可以创建任何遵循原型接口的对象的副本。

原型注册表实现

经典创建型设计模式:原型模式

原型注册表提供了一种方便访问常用原型的方法。它存储了一组预先构建好的对象,可以随时进行复制。最简单的原型注册表是一个名称到原型的哈希映射。然而,如果您需要比简单名称更好的搜索条件,可以构建一个更强大的注册表版本。

伪代码

在这个例子中,原型模式允许您创建几何对象的精确副本,而不需要将代码与它们的类耦合在一起。

经典创建型设计模式:原型模式

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

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

// Base prototype.
abstract class Shape is
    field X: int
    field Y: int
    field color: string

    // A regular constructor.
    constructor Shape() is
        // ...

    // The prototype constructor. A fresh object is initialized
    // with values from the existing object.
    constructor Shape(source: Shape) is
        this()
        this.X = source.X
        this.Y = source.Y
        this.color = source.color

    // The clone operation returns one of the Shape subclasses.
    abstract method clone():Shape


// Concrete prototype. 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.           

适用性

1、使用原型模式的情况是,当您的代码不应该依赖于需要复制的对象的具体类时。

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

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

2、在以下情况下使用该模式:当您希望减少仅在初始化其各自对象的方式上有所不同的子类的数量。

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

原型模式允许您使用以各种方式配置的预构建对象集作为原型。客户端不需要实例化与某个配置匹配的子类,而是可以查找适当的原型并进行克隆。

如何实现

  1. 创建原型接口,并在其中声明克隆方法。或者,如果已经存在一个类层次结构,可以将该方法添加到所有类的定义中。
  2. 原型类必须定义一个接受该类对象作为参数的备选构造函数。该构造函数必须将传入对象的所有字段值从原型对象复制到新创建的实例中。如果更改了子类,则必须调用父类构造函数,让超类处理其私有字段的克隆。如果您的编程语言不支持方法重载,您将无法创建单独的“原型”构造函数。因此,将对象的数据复制到新创建的克隆中必须在克隆方法中执行。但是,将此代码放在常规构造函数中更安全,因为在调用 new 操作符后,返回的对象将完全配置。
  3. 克隆方法通常只包含一行代码:使用原型构造函数的原型版本运行 new 操作符。请注意,每个类都必须显式地覆盖克隆方法,并使用自己的类名和 new 操作符。否则,克隆方法可能会生成一个父类的对象。
  4. 可选地,创建一个集中的原型注册表,用于存储经常使用的原型目录。您可以将注册表实现为一个新的工厂类,或将其放置在基础原型类中,并提供一个用于获取原型的静态方法。此方法应该根据客户端代码传递给该方法的搜索条件来搜索原型。条件可以是简单的字符串标签,也可以是一组复杂的搜索参数。找到适当的原型后,注册表应克隆它并将副本返回给客户端。最后,将对子类构造函数的直接调用替换为对原型注册表的工厂方法的调用。

Python示例

import copy


class SelfReferencingEntity:
    def __init__(self):
        self.parent = None

    def set_parent(self, parent):
        self.parent = parent


class SomeComponent:
    """
    Python provides its own interface of Prototype via `copy.copy` and
    `copy.deepcopy` functions. And any class that wants to implement custom
    implementations have to override `__copy__` and `__deepcopy__` member
    functions.
    """

    def __init__(self, some_int, some_list_of_objects, some_circular_ref):
        self.some_int = some_int
        self.some_list_of_objects = some_list_of_objects
        self.some_circular_ref = some_circular_ref

    def __copy__(self):
        """
        Create a shallow copy. This method will be called whenever someone calls
        `copy.copy` with this object and the returned value is returned as the
        new shallow copy.
        """

        # First, let's create copies of the nested objects.
        some_list_of_objects = copy.copy(self.some_list_of_objects)
        some_circular_ref = copy.copy(self.some_circular_ref)

        # Then, let's clone the object itself, using the prepared clones of the
        # nested objects.
        new = self.__class__(
            self.some_int, some_list_of_objects, some_circular_ref
        )
        new.__dict__.update(self.__dict__)

        return new

    def __deepcopy__(self, memo=None):
        """
        Create a deep copy. This method will be called whenever someone calls
        `copy.deepcopy` with this object and the returned value is returned as
        the new deep copy.

        What is the use of the argument `memo`? Memo is the dictionary that is
        used by the `deepcopy` library to prevent infinite recursive copies in
        instances of circular references. Pass it to all the `deepcopy` calls
        you make in the `__deepcopy__` implementation to prevent infinite
        recursions.
        """
        if memo is None:
            memo = {}

        # First, let's create copies of the nested objects.
        some_list_of_objects = copy.deepcopy(self.some_list_of_objects, memo)
        some_circular_ref = copy.deepcopy(self.some_circular_ref, memo)

        # Then, let's clone the object itself, using the prepared clones of the
        # nested objects.
        new = self.__class__(
            self.some_int, some_list_of_objects, some_circular_ref
        )
        new.__dict__ = copy.deepcopy(self.__dict__, memo)

        return new


if __name__ == "__main__":

    list_of_objects = [1, {1, 2, 3}, [1, 2, 3]]
    circular_ref = SelfReferencingEntity()
    component = SomeComponent(23, list_of_objects, circular_ref)
    circular_ref.set_parent(component)

    shallow_copied_component = copy.copy(component)

    # Let's change the list in shallow_copied_component and see if it changes in
    # component.
    shallow_copied_component.some_list_of_objects.append("another object")
    if component.some_list_of_objects[-1] == "another object":
        print(
            "Adding elements to `shallow_copied_component`'s "
            "some_list_of_objects adds it to `component`'s "
            "some_list_of_objects."
        )
    else:
        print(
            "Adding elements to `shallow_copied_component`'s "
            "some_list_of_objects doesn't add it to `component`'s "
            "some_list_of_objects."
        )

    # Let's change the set in the list of objects.
    component.some_list_of_objects[1].add(4)
    if 4 in shallow_copied_component.some_list_of_objects[1]:
        print(
            "Changing objects in the `component`'s some_list_of_objects "
            "changes that object in `shallow_copied_component`'s "
            "some_list_of_objects."
        )
    else:
        print(
            "Changing objects in the `component`'s some_list_of_objects "
            "doesn't change that object in `shallow_copied_component`'s "
            "some_list_of_objects."
        )

    deep_copied_component = copy.deepcopy(component)

    # Let's change the list in deep_copied_component and see if it changes in
    # component.
    deep_copied_component.some_list_of_objects.append("one more object")
    if component.some_list_of_objects[-1] == "one more object":
        print(
            "Adding elements to `deep_copied_component`'s "
            "some_list_of_objects adds it to `component`'s "
            "some_list_of_objects."
        )
    else:
        print(
            "Adding elements to `deep_copied_component`'s "
            "some_list_of_objects doesn't add it to `component`'s "
            "some_list_of_objects."
        )

    # Let's change the set in the list of objects.
    component.some_list_of_objects[1].add(10)
    if 10 in deep_copied_component.some_list_of_objects[1]:
        print(
            "Changing objects in the `component`'s some_list_of_objects "
            "changes that object in `deep_copied_component`'s "
            "some_list_of_objects."
        )
    else:
        print(
            "Changing objects in the `component`'s some_list_of_objects "
            "doesn't change that object in `deep_copied_component`'s "
            "some_list_of_objects."
        )

    print(
        f"id(deep_copied_component.some_circular_ref.parent): "
        f"{id(deep_copied_component.some_circular_ref.parent)}"
    )
    print(
        f"id(deep_copied_component.some_circular_ref.parent.some_circular_ref.parent): "
        f"{id(deep_copied_component.some_circular_ref.parent.some_circular_ref.parent)}"
    )
    print(
        "^^ This shows that deepcopied objects contain same reference, they "
        "are not cloned repeatedly."
    )           

继续阅读