[Python設計模式] 第26章 千人千面,內在共享——享元模式

github地址:https://github.com/cheesezh/python_design_patternspython

背景

有6個客戶想作產品展現網站,其中3個想作整天貓商城那樣的「電商風格」展現頁面,其中3個想作成博客園那樣的「博客風格」展現博客。應該如何實現?git

class WebSite():
    
    def __init__(self, name):
        self.name = name
        
    def use(self):
        print("網站風格:", self.name)
        
def main():
    web1 = WebSite("電商風格")
    web1.use()
    
    web2 = WebSite("電商風格")
    web2.use()
    
    web3 = WebSite("電商風格")
    web3.use()
    
    web4 = WebSite("博客風格")
    web4.use()
    
    web5 = WebSite("博客風格")
    web5.use()
    
    web6 = WebSite("博客風格")
    web6.use()
    
main()
網站風格: 電商風格
網站風格: 電商風格
網站風格: 電商風格
網站風格: 博客風格
網站風格: 博客風格
網站風格: 博客風格

點評

根據上邊的代碼,若是要作三個「電商風格」,三個「博客風格」的網站,須要六個網站類的實例,而實際上它們本質上都是同樣的代碼,若是網站增多,實例數量也會增多,這對服務器的資源浪費很嚴重。github

如今各個大型博客網站,電子商務網站,每個博客或者商家都是一個小型網站,它們根據用戶ID號的不一樣,來區分不一樣的用戶,具體數據和模版能夠不一樣,可是代碼核心和數據庫倒是共享的。web

這就須要用到享元模式。數據庫

享元模式

享元模式,運用共享技術有效的支持大量細粒度的對象。主要包括如下幾個類:服務器

from abc import ABCMeta, abstractmethod


class Flyweight():
    """
    Flyweight類,它是全部具體享元類的超類或接口,經過這個接口,Flyweight能夠接受並做用於外部狀態。
    """
    __metaclass__ = ABCMeta
    
    @abstractmethod
    def operation(self, extrinsicstate):
        pass
    
    
class ConcreteFlyweight(Flyweight):
    """
    ConcreteFlyweight是繼承Flyweight超類或者是想Flyweight接口,併爲內部狀態增長存儲空間
    """
    def operation(self, extrinsicstate):
        print("具體Flyweight:", extrinsicstate)
        
        
class UnsharedConcreteFlyweight(Flyweight):
    """
    UnsharedConcreteFlyweight是指那些不須要共享的Flyweight子類。由於Flyweight接口,共享成爲可能,但它並不強制共享
    """
    def operation(self, extrinsicstate):
        print("不共享的具體Flyweight:", extrinsicstate)
        
        
class FlyweightFactory():
    """
    FlyweightFactory是一個享元工廠,用來建立並管理Flyweight對象。它主要是用來確保合理地共享Flyweight,當用戶請求一個
    Flyweight時,FlyweightFactory對象提供一個已建立的實例或者建立一個(若是不存在的話)。
    """
    def __init__(self):
        self.flyweights = dict()
        self.flyweights['X'] = ConcreteFlyweight()
        self.flyweights['Y'] = ConcreteFlyweight()
        self.flyweights['Z'] = ConcreteFlyweight()
        
    def get_flyweight(self, key):
        return self.flyweights[key]
    

def main():
    # 代碼外部狀態
    extrinsicstate = 22
    
    f = FlyweightFactory()
    
    fx = f.get_flyweight("X")
    extrinsicstate -= 1
    fx.operation(extrinsicstate)
    
    fy = f.get_flyweight("Y")
    extrinsicstate -= 1
    fy.operation(extrinsicstate)
    
    fz = f.get_flyweight("Z")
    extrinsicstate -= 1
    fy.operation(extrinsicstate)
    
    uf = UnsharedConcreteFlyweight()
    extrinsicstate -= 1
    uf.operation(extrinsicstate)
    
main()
具體Flyweight: 21
具體Flyweight: 20
具體Flyweight: 19
不共享的具體Flyweight: 18

點評

上述代碼中,FlyweightFactory根據客戶需求返回早已生成好的對象,可是實際上不必定須要,徹底能夠初始化時什麼也不作,到須要時,再判斷對象是否爲null來決定是否實例化;網站

爲何會有UnsharedConcreteFlyweight存在呢?這是由於儘管咱們大部分時間都須要共享對象來下降內存損耗,但個別時候也有可能不須要共享的,那麼此時的UnsharedConcreteFlyweight就有存在的必要了,它能夠解決那些不須要共享對象的問題。設計

網站共享代碼

from abc import ABCMeta, abstractmethod


class WebSite():
    """
    網站抽象類:Flyweight類,它是全部具體享元類的超類或接口,經過這個接口,Flyweight能夠接受並做用於外部狀態。
    """
    __metaclass__ = ABCMeta
    
    @abstractmethod
    def use(self):
        pass
    
    
class ConcreteWebSite(WebSite):
    """
    具體網站類:ConcreteFlyweight是繼承Flyweight超類或者是想Flyweight接口,併爲內部狀態增長存儲空間
    """
    def __init__(self, name):
        self.name = name
        
    def use(self):
        print("網站風格:", self.name)
        
                
class WebSiteFactory():
    """
    網站工廠類:FlyweightFactory是一個享元工廠,用來建立並管理Flyweight對象。它主要是用來確保合理地共享Flyweight,
    當用戶請求一個Flyweight時,FlyweightFactory對象提供一個已建立的實例或者建立一個(若是不存在的話)。
    """
    def __init__(self):
        self.flyweights = dict()
        
    def get_website(self, key):
        if key not in self.flyweights:
            self.flyweights[key] = ConcreteWebSite(key)
        return self.flyweights[key]
    

def main():
    f = WebSiteFactory()
    
    fx = f.get_website("電商風格")
    fx.use()
    
    fy = f.get_website("電商風格")
    fy.use()
    
    fz = f.get_website("電商風格")
    fz.use()
    
    fa = f.get_website("博客風格")
    fa.use()
    
    fb = f.get_website("博客風格")
    fb.use()
    
    fc = f.get_website("博客風格")
    fc.use()
    print("網站風格總數:", len(f.flyweights))
    
main()
網站風格: 電商風格
網站風格: 電商風格
網站風格: 電商風格
網站風格: 博客風格
網站風格: 博客風格
網站風格: 博客風格
網站風格總數: 2

點評

這樣寫基本實現了享元模式共享對象的目的,也就是,不管建立多少個網站,只要是「電商風格」,那就都同樣,只要是「博客風格」,也都同樣。可是給不一樣企業建立網站,它們的數據確定會不一樣,因此上述代碼沒有體香對象間的不一樣,只體現了共享的部分。code

內部狀態和外部狀態

在享元對象內部而且不會隨環境改變而改變的共享部分,能夠成爲是享元對象的內部狀態;對象

隨着環境改變而改變,不可共享的狀態就是享元對象的外部狀態;

享元模式能夠避免大量很是類似類的開銷。在程序設計中,有時須要生成大量細粒度的類實例來表示數據。若是能發現這些實例除了幾個參數外基本上都是相同的,有時候就可以大幅度地減小須要實例化的類的數量。若是能把那些參數轉移到類實例的外面,在方法調用時將它們傳遞進來,就能夠經過共享大幅度地減小單個實例的數目。

享元模式Flyweight執行時所需的狀態是有內部的,也可能有外部的,內部狀態存儲於ConcreteFlyweight對象之中,而外部狀態則應該考慮有客戶端對象存儲或計算,當調用Flyweight對象的操做時,將該狀態傳遞給它。

在網站的例子中,客戶帳號就是外部狀態,應該由專門的對象來處理。

帶有外部狀態的版本

from abc import ABCMeta, abstractmethod

class User():
    """
    用戶類,網站的客戶帳號,是「網站類」的外部狀態
    """
    def __init__(self, name):
        self.name = name

class WebSite():
    """
    網站抽象類:Flyweight類,它是全部具體享元類的超類或接口,經過這個接口,Flyweight能夠接受並做用於外部狀態。
    """
    __metaclass__ = ABCMeta
    
    @abstractmethod
    def use(self, user):
        pass
    
    
class ConcreteWebSite(WebSite):
    """
    具體網站類:ConcreteFlyweight是繼承Flyweight超類或者是想Flyweight接口,併爲內部狀態增長存儲空間
    """
    def __init__(self, name):
        self.name = name
        
    def use(self, user):
        print(user.name, "- 網站風格:", self.name)
        
                
class WebSiteFactory():
    """
    網站工廠類:FlyweightFactory是一個享元工廠,用來建立並管理Flyweight對象。它主要是用來確保合理地共享Flyweight,
    當用戶請求一個Flyweight時,FlyweightFactory對象提供一個已建立的實例或者建立一個(若是不存在的話)。
    """
    def __init__(self):
        self.flyweights = dict()
        
    def get_website(self, key):
        if key not in self.flyweights:
            self.flyweights[key] = ConcreteWebSite(key)
        return self.flyweights[key]
    

def main():
    f = WebSiteFactory()
    
    fx = f.get_website("電商風格")
    fx.use(User("賀賀"))
    
    fy = f.get_website("電商風格")
    fy.use(User("曼曼"))
    
    fz = f.get_website("電商風格")
    fz.use(User("云云"))
    
    fa = f.get_website("博客風格")
    fa.use(User("靈靈"))
    
    fb = f.get_website("博客風格")
    fb.use(User("依依"))
    
    fc = f.get_website("博客風格")
    fc.use(User("靈依"))
    print("網站風格總數:", len(f.flyweights))
    
main()
賀賀 - 網站風格: 電商風格
曼曼 - 網站風格: 電商風格
云云 - 網站風格: 電商風格
靈靈 - 網站風格: 博客風格
依依 - 網站風格: 博客風格
靈依 - 網站風格: 博客風格
網站風格總數: 2

總結

何時須要考慮使用享元模式呢?

  • 若是一個應用程序使用了大量的對象,而大量的這些對象形成了很大的存儲開銷時就應該考慮使用
  • 對象的大多數狀態都是內部狀態,若是能夠刪除對象的外部狀態,那麼能夠用相對較少的共享對象取代不少組對象

在實際使用中,享元模式到底能達到什麼效果呢?

由於使用了享元模式,因此有了共享對象,實例總數就大大減小了,若是共享的對象越多,存儲節約也就越多,節約量隨着共享狀態的增多而增大。

須要注意的是,享元模式須要維護一個記錄了系統已有的全部享元的列表,而這自己須要耗費資源,另外享元模式會使系統變得更加複雜。

相關文章
相關標籤/搜索