第 3 章 原型模式

每个副本被称为一个克隆,是某个时间点原有对象的一个完全副本。这里时间是一个重要因素。因为它会影响克隆所包含的内容。

注意区分引用与副本。

原型设计模式(Prototype design pattern)帮助我们创建对象的克隆,其最简单的形式就是一个 clone() 函数,接受一个对象作为输入参数,返回输入对象的一个副本。在 Python 中,这可以使用 copy.deepcopy() 函数来完成。

3.1 现实生活的例子

有丝分裂,即细胞分裂的过程,是生物克隆的一个例子。

3.2 软件的例子

很多 Python 应用都使用了原型模式,但几乎都不称之为原型模式,因为对象克隆是编程语言的一个内置特性。

可视化工具套件(Visualization Toolkit,VTK)是原型模式的一个应用。VTK 是一个开源的跨平台系统,用于三维计算机图形/图片处理以及可视化。

另一个使用原型模式的项目是 music21。根据该项目页面所述,“music21 是一组工具,帮助 学者和其他积极的听众快速简便地得到音乐相关问题的答案”。

3.3 应用案例

当我们已有一个对象,并希望创建该对象的一个完整副本时,原型模式就派上用场了。

另一个案例是,当我们想复制一个复杂对象时,使用原型模式会很方便。对于复制复杂对象,我们可以将对象当作是从数据库中获取的,并引用其他一些也是从数据库中获取的对象。若通过多次重复查询数据来创建一个对象,则要做很多工作。在这种场景下使用原型模式要方便得多。

我们可以引入数据共享和写时复制一类的技术来优化性能(例如, 减小克隆对象的创建时间)和内存使用。如果可用资源有限(例如,嵌入式系统)或性能至关重要(例如,高性能计算),那么使用浅副本可能更佳。

3.4 实现

有关书籍的一个例子:

import copy
from collections import OrderedDict

# 产品类
class Book:
    def __init__(self, name, authors, price, **rest):
        '''rest的例子有:出版商,长度,标签,出版日期'''
        self.name = name
        self.authors = authors
        self.price = price      # 单位为美元
        self.__dict__.update(rest)
    def __str__(self):
        mylist = []
        ordered = OrderedDict(sorted(self.__dict__.items()))
        for i in ordered.keys():
            mylist.append('{}: {}'.format(i, ordered[i]))
            if i == 'price':
                mylist.append('$')
            mylist.append('\n')
        return ''.join(mylist)

#  原型设计
class Prototype:
    def __init__(self):
        self.objects = dict()
    def register(self, identifier, obj):
        self.objects[identifier] = obj
    def unregister(self, identifier):
        del self.objects[identifier]
    # 通过 attr 属性来控制要改变的变量
    def clone(self, identifier, **attr):
        found = self.objects.get(identifier)
        if not found:
            raise ValueError('Incorrect object identifier: {}'.format(identifier))
        obj = copy.deepcopy(found)
        obj.__dict__.update(attr)
        return obj


def main():
    b1 = Book('The C Programming Language', ('Brian W. Kernighan', 'Dennis M.Ritchie'), price=118, publisher='Prentice Hall',
              length=228, publication_date='1978-02-22', tags=('C', 'programming', 'algorithms', 'data structures'))

    prototype = Prototype()
    cid = 'k&r-first'
    prototype.register(cid, b1)
    b2 = prototype.clone(cid, name='The C Programming Language(ANSI)', price=48.99,
                         length=274, publication_date='1988-04-01', edition=2)

if __name__ == '__main__':
    main()

3.5 小结

  • 当创建一个浅副本时,副本依赖引用

    • 这种情况中,我们关注提升应用性能和优化内存使用,在对象之间引入数据共享,但需要小心地修改数据,因为所有变更对所有副本都是可见的。

  • 当创建一个深副本时,副本复制所有东西

    • 这种情况中,我们希望能够对一个副本进行更改而不会影响其他对象。

Last updated