万物之中, 希望至美.

设计模式之享元模式

2018.10.19

OOP中容易出现对象创建带来的性能和内存占用问题,当我们想要优化内存使用提高应用性能之时,可以使用享元模式。而想要使享元模式有效,需要满足以下几个条件:

  • 需要使用大量对象(Python 中可以使用__slots__节省内存使用)
  • 对象太多难以存储或解析大量对象
  • 对象识别不是特别重要,共享对象中对象比较会失败

通常情况下,会使用对象池技术来实现共享对象,比如数据库中经常使用连接池来减少开销,预先建立一些连接池,每次取一个连接和数据库交互。

一般来说,在应用需要创建大量的计算代价大但共享许多属性的对象时,可以使用享元。重点在于将不可变(可共享)的属性与可变的属性区分开。这里以一个树渲染器,支持三种不同的树家族为例:

import random
from enum import Enum

TreeType = Enum('TreeType', ('apple_tree', 'cherry_tree', 'peach_tree'))


class Tree:
    pool = dict()

    def __new__(cls, tree_type):
        obj = cls.pool.get(tree_type, None)
        if not obj:
            obj = object.__new__(cls)
            cls.pool[tree_type] = obj
            obj.tree_type = tree_type
        return obj

    def render(self, age, x, y):
        print(f'render a tree of type {self.tree_type}'
              f' and age {age} at ({x}, {y})')


def main():
    rnd = random.Random()
    age_min, age_max = 1, 30  # 单位为年
    min_point, max_point = 0, 100
    tree_counter = 0

    for _ in range(10):
        t1 = Tree(TreeType.apple_tree)
        t1.render(rnd.randint(age_min, age_max),
                  rnd.randint(min_point, max_point),
                  rnd.randint(min_point, max_point))
        tree_counter += 1

    for _ in range(3):
        t2 = Tree(TreeType.cherry_tree)
        t2.render(rnd.randint(age_min, age_max),
                  rnd.randint(min_point, max_point),
                  rnd.randint(min_point, max_point))
        tree_counter += 1

    for _ in range(5):
        t3 = Tree(TreeType.peach_tree)
        t3.render(rnd.randint(age_min, age_max),
                  rnd.randint(min_point, max_point),
                  rnd.randint(min_point, max_point))
        tree_counter += 1

    print('trees rendered: {}'.format(tree_counter))
    print('trees actually created: {}'.format(len(Tree.pool)))
    t4 = Tree(TreeType.cherry_tree)
    t5 = Tree(TreeType.cherry_tree)
    t6 = Tree(TreeType.apple_tree)
    print('{} == {}? {}'.format(id(t4), id(t5), id(t4) == id(t5)))
    print('{} == {}? {}'.format(id(t5), id(t6), id(t5) == id(t6)))


if __name__ == '__main__':
    main()
comments powered by Disqus