天天看點

python設計模式之享元模式享元模式

享元模式

Flyweight模式,顧名思義,就是共享中繼資料,在python這個動态語言中可以提高程式性能和效率,比如從一個檔案讀取很多的字元串,而這些字元串必定重複,是以可以使用這個模式将其存在一個pool中

python的例子(我将提出一系列的例子不同方式實作這個功能)

普通青年版,看了Gof,可能就會有這樣基礎的一段代碼:

class Spam(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

class SpamFactory(object):
    def  __init__(self):
        self.__instances = dict()

    def get_instance(self, a, b):
        # 在執行個體化後生成一個字典,當新的元祖對不存在就緩存起來
        if (a, b) not in self.__instances:
            self.__instances[(a, b)] = Spam(a, b)
        return self.__instances[(a, b)]

# 這個和上面的意思完全一樣, 這是為了在最後斷言下2個工廠緩存的資料是不是能共享
class Egg(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

class EggFactory(object):
    def __init__(self):
        self.__instances = dict()

    def get_instance(self, x, y):
        if (x, y) not in self.__instances:
            self.__instances[(x, y)] = Egg(x, y)
        return self.__instances[(x, y)]

spamFactory = SpamFactory()
eggFactory = EggFactory()

assert spamFactory.get_instance(, ) is spamFactory.get_instance(, )
assert eggFactory.get_instance('a', 'b') is eggFactory.get_instance('a', 'b')
# spamFactory存儲的這個元祖和eggFactory存儲的不是一個東西
assert spamFactory.get_instance(, ) is not eggFactory.get_instance(, )
           

上面的是最簡單能想到的,也是我見過用得最多的一種, 可是還有夢想青年版, 因為上面的東西是可以抽象出來的, 比如上面隻能傳入a,b2個,再多了就不行了,其他的風格也不行,比如我想緩存鍵值對,是以想起來了args和*kwargs

class FlyweightFactory(object):
    def __init__(self, cls):
        self._cls = cls
        self._instances = dict()
    # 使用*args, **kargs這樣的方式抽象實作的cls的參數數量和類型
    def get_instance(self, *args, **kargs):
        # 如果鍵在字典中,傳回這個鍵所對應的值。如果鍵不在字典中,向字典 中插入這個鍵
        return self._instances.setdefault(
                                (args, tuple(kargs.items())),
                                self._cls(*args, **kargs))


class Spam(object):
    # 我這裡實作的是3個參數,這個随意
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

class Egg(object):
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z


SpamFactory = FlyweightFactory(Spam)
EggFactory = FlyweightFactory(Egg)

assert SpamFactory.get_instance(, , ) is SpamFactory.get_instance(, , )
assert EggFactory.get_instance('a', 'b', 'c') is EggFactory.get_instance('a', 'b', 'c')
assert SpamFactory.get_instance(, , ) is not EggFactory.get_instance(, , )
           

然後就是我最喜歡的風格,裝飾器解決這個問題,算是老土的文藝青年吧

# 這個是裝飾器,主要用來将操作工廠當參數傳入,攔截操作工廠的調用
class flyweight(object):
    def __init__(self, cls):
        self._cls = cls
        self._instances = dict()
    # 重載括号操作符, 你想啊,加了裝飾器就會調用,也就會觸發__call__
    def __call__(self, *args, **kargs):
        return self._instances.setdefault(
                                    (args, tuple(kargs.items())),
                                    self._cls(*args, **kargs))


@flyweight
class Spam(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b


@flyweight
class Egg(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y


assert Spam(, ) is Spam(, )
assert Egg('a', 'b') is Egg('a', 'b')
assert Spam(, ) is not Egg(, )
           

但是我們實在态out了,首先,沒必要把裝飾器搞成一個類,完全可以使用函數式程式設計

# instances是閉包,好懂吧
def flyweight(cls):
    instances = dict()
    return lambda *args, **kargs: instances.setdefault(
                                            (args, tuple(kargs.items())),
                                            cls(*args, **kargs))


@flyweight
class Spam(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b


@flyweight
class Egg(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y


assert Spam(, ) is Spam(, )
assert Egg('a', 'b') is Egg('a', 'b')
assert Spam(, ) is not Egg(, )
           

該是剛進城的文藝小青版版了,這裡用了一個東西Mixin: 給某個具體的類一些它需要的具體功能

# 這是Mixin類,它提供了get_instance, 誰需要這個方法誰繼承,不需要不繼承
class FlyweightMixin(object):

    _instances = dict()

    @classmethod
    def get_instance(cls, *args, **kargs):
        return cls._instances.setdefault(
                                (cls, args, tuple(kargs.items())), 
                                cls(*args, **kargs))


class Spam(FlyweightMixin):

    def __init__(self, a, b):
        self.a = a
        self.b = b


class Egg(FlyweightMixin):

    def __init__(self, x, y):
        self.x = x
        self.y = y


assert Spam.get_instance(, ) is Spam.get_instance(, )
assert Egg.get_instance('a', 'b') is Egg.get_instance('a', 'b')
assert Spam.get_instance(, ) is not Egg.get_instance(, )
           

唯一不爽的是調用方式:XX.get_instance(A, B),不夠高端

class FlyweightMixin(object):
    _instances = dict()
    def __init__(self, *args, **kargs):
        # 隻想被繼承不想被初始化
        raise NotImplementedException
    # 重載執行個體化觸發的__new__
    def __new__(cls, *args, **kargs):
        return cls._instances.setdefault(
                    (cls, args, tuple(kargs.items())),
                    super(type(cls), cls).__new__(cls, *args, **kargs))


class Spam(FlyweightMixin):

    def __init__(self, a, b):
        self.a = a
        self.b = b


class Egg(FlyweightMixin):

    def __init__(self, x, y):
        self.x = x
        self.y = y


assert Spam(, ) is Spam(, )
assert Egg('a', 'b') is Egg('a', 'b')
assert Spam(, ) is not Egg(, )
           

嗯, 這樣就和以前一樣好看了,可是 什麼文藝青年? 差得很遠, 一個想成為文藝青年的炫技版:

@classmethod
def _get_instance(cls, *args, **kargs):
    return cls.__instances.setdefault(
                                (args, tuple(kargs.items())),
                                super(type(cls), cls).__new__(*args, **kargs))
# 其實繞了個圈子在操作工廠執行個體化的時候攔截執行上面的類方法
def flyweight(decoree):
    decoree.__instances = dict()
    decoree.__new__ = _get_instance
    return decoree


@flyweight
class Spam(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b


@flyweight
class Egg(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y


assert Spam(, ) is Spam(, )
assert Egg('a', 'b') is Egg('a', 'b')
assert Spam(, ) is not Egg(, )
           

無語的文藝,好吧,現在就大衆經常看見的文藝青年(穿着文藝,其實骨子裡不文藝)版

# 賦予建立類的控制權,也就是python的元類metaclass
class MetaFlyweight(type):
    def __init__(cls, *args, **kargs):
        type.__init__(cls, *args, **kargs)
        cls.__instances = dict()
        # 當你執行個體化cls的時候,執行個體的結果其實是執行_get_instance方法
        cls.__new__ = cls._get_instance

    def _get_instance(cls, *args, **kargs):
        return cls.__instances.setdefault(
                                    (args, tuple(kargs.items())),
                                    super(cls, cls).__new__(*args, **kargs))


class Spam(object):
    # 提供你生成這個類的模闆
    __metaclass__ = MetaFlyweight

    def __init__(self, a, b):
        self.a = a
        self.b = b


class Egg(object):
    __metaclass__ = MetaFlyweight

    def __init__(self, x, y):
        self.x = x
        self.y = y


assert Spam(, ) is Spam(, )
assert Egg('a', 'b') is Egg('a', 'b')
assert Spam(, ) is not Egg(, )
           

這個當然也可以搞成類方法的風格,我隻列出關鍵的代碼

@classmethod
def _get_instance(cls, *args, **kargs):
    return cls.__instances.setdefault(
                                (args, tuple(kargs.items())),
                                super(type(cls), cls).__new__(*args, **kargs))

def metaflyweight(name, parents, attrs):
    cls = type(name, parents, attrs)
    cls.__instances = dict()
    cls.__new__ = _get_instance
    return cls
           

好吧,終極的文藝青年版- 這是一個純函數式使用元類的方法:

metaflyweight = lambda name, parents, attrs: type(
        name,
        parents,
        dict(attrs.items() + [
            ('__instances', dict()),
            ('__new__', classmethod(
                lambda cls, *args, **kargs: cls.__instances.setdefault(
                                tuple(args),
                                super(type(cls), cls).__new__(*args, **kargs))
                )
            )
        ])
    )


class Spam(object):
    __metaclass__ = metaflyweight

    def __init__(self, a, b):
        self.a = a
        self.b = b


class Egg(object):
    __metaclass__ = metaflyweight

    def __init__(self, x, y):
        self.x = x
        self.y = y


assert Spam(, ) is Spam(, )
assert Egg('a', 'b') is Egg('a', 'b')
assert Spam(, ) is not Egg(, )
           

你到了那個境界呢?