2015年12月25日 星期五

[ Python 文章收集 ] python實踐設計模式(二)Builder,Singleton,Prototype

Source From Here 
4. Builder模式 
個人理解,如果說工廠模式旨在選擇創建哪一類的實例,而 Builder 模式的重點是封裝一個實例的複雜創建過程。它可以將一個產品的內部表象與產品的生成過程分割開來,從而可以使一個建造過程生成具有不同的內部表象的產品對象。也就是說,建造的步驟可以穩定不變,但是每一步的內部表象可以靈活變化。UML 圖如下: 
Builder: 為創建 Product 對象的各個部件指定抽象接口,python中為父類。
ConcreteBuilder: 實現 Builder 的接口以構造和裝配該產品的各個部件,定義並明確它所創建的表示,並提供一個檢索產品的接口,也就是返回產品類的方法。
Director: 構造一個使用Builer接口的對象,該對像中定義了建造對象的步驟順序。
Product: 表示被構造的複雜對象。ConcreteBuilder創建該產品的內部表示並定義它的具體裝配方法,包含定義組成部件的類,以及將這些部件裝配成最終產品的接口。

一個比較貼切的例子: 
要建一座房子,可是我不知道怎麼蓋,於是我需要找建築隊的工人他們會,還得找個設計師,他知道怎麼設計,我還要確保建築隊的工人聽設計師的領導,而設計師本身不干活,只下命令,這裡砌一堵牆,這裡砌一扇門,這樣建築隊的工人開始建設,最後,我可以向建築隊的工人要房子了。在這個過程中,設計師是什麼也沒有,除了他在腦子裡的設計和命令,所以要房子也是跟建築隊的工人要。在這個例子中 Director 是設計師,Builder 代表建築隊工人會的建築技能,ConcreteBuilder 工人建築技能的具體操作,Product就是我要蓋的房子。

下面代碼的例子,建築隊的工人有砌牆,裝窗戶,裝門的技能以及交房的安排,設計師決定了建設房屋的安排和步驟,現在我要通過2個建築隊的民工,建2所房子,實例代碼如下: 
- Builder_test.py 
  1. #!/usr/bin/env python  
  2. class Builder:  
  3.     def BuildWall(self):  
  4.         pass  
  5.     def BuildDoor(self):  
  6.         pass  
  7.     def BuildWindow(self):  
  8.         pass  
  9.     def GetRoom(self):  
  10.         pass  
  11. class ConcreteBuilder1(Builder):  
  12.     def __init__ (self):  
  13.         self.__Room= []  
  14.     def BuildWall(self):  
  15.         self.__Room.append( " Builder1 Build the wall. " )  
  16.     def BuildDoor(self):  
  17.         self.__Room.append( " Builder1 Build the door. " )  
  18.     def BuildWindow(self):  
  19.         self.__Room.append( " Builder1 Build the window. " )  
  20.     def GetRoom(self):  
  21.         return self.__Room  
  22. class ConcreteBuilder2(Builder):  
  23.     def __init__( self):  
  24.         self.__Room= []  
  25.     def BuildWall(self):  
  26.         self.__Room.append( " Builder2 Build the wall. " )  
  27.     def BuildDoor(self):  
  28.         self.__Room.append( " Builder2 Build the door. " )  
  29.     def BuildWindow(self):  
  30.         self.__Room.append( " Builder2 Build the window. " )  
  31.     def GetRoom(self):  
  32.         return self.__Room  
  33. class Director:  
  34.     def __init__(self,Builder) :  
  35.         self.__build= Builder  
  36.     def order(self):  
  37.         self.__build.BuildWall()  
  38.         self.__build.BuildWindow()  
  39.         self.__build.BuildDoor()  
  40. if __name__ == "__main__" :  
  41.   
  42.     builder1 = ConcreteBuilder1()  
  43.     director= Director(builder1)  
  44.     director.order()  
  45.     print builder1.GetRoom()  
  46.   
  47.     builder2= ConcreteBuilder2()  
  48.     director= Director(builder2)  
  49.     director.order()  
  50.     print builder2.GetRoom()  
注: 
因為 python 沒有 private 類型的成員,不過我們可以用命名為 __name 的變量代替,例如上例中的 __Room,為什麼這樣可以呢,我們用
  1. builder1=ConcreteBuilder1()   
  2. print dir(builder1)  
打印語句看出如下圖,

__name 實例化後變為 __ConcreteBuilder1__name ,就是避免外界對 __name 屬性的修改,從而達到了封閉性。更多請參考 "Private Variables and Class-local References"

5. Singleton 模式 
Singleton模式要求一個類有且僅有一個實例,並且提供了一個全局的訪問點,UML 如下: 
 

單例模式雖然不復雜,我一直認為這個模式是最簡單的,當我想用python實現的時候確犯難了,這篇文章也足足用了2星期才寫出來,期間各種查資料(省略1000個字),下面就來說說實現方法。先說以前比較熟悉的像C#這樣的語言,一般的實現方法是: 
1. 有一個私有的無參構造函數,這可以防止其他類實例化它。
2. 單例類被定義為sealed,目的是單例類也不被繼承,如果單例類允許繼承那麼每個子類都可以創建實例,這就違背了Singleton模式“唯一實例”的初衷,所以為了保險起見可以把該類定義成不允許派生,但沒有要求一定要這樣定義。
3. 一個靜態的變量用來保存單實例的引用。
4. 一個公有的靜態方法用來獲取單實例的引用,如果實例為 null 即創建一個。

上面是我熟悉的Singleton模式的創建方法,但是對於 python,既沒有 static 類型,也沒有私有方法和 sealed 修飾的類,如何實現呢?關於私有方法和屬性,我前面已經提到可以用 __name 形式為名稱定義方法名和屬性名來解決; 接著來幾種實作方式: 
- 利用 isinstance() 或 issubclass() 
isinstance(object, classinfo) 如果 object 是 classinfo 的一個實例或是子類,或者如果 classinfo 和 object 的類型是對象,或是該類型的對象的子類,返回 true; issubclass(class, classinfo) 如果class 是 classinfo 的一個子類返回 true。下面是利用 isinstance 實現的 Singleton 模式範例: 
- Singleton_test.py 
  1. #!/usr/bin/env python  
  2. class Singleton:  
  3.     __singleton = None  
  4.     @classmethod  
  5.     def getSingleton(cls):  
  6.         if not isinstance(cls.__singleton,cls):  
  7.             cls.__singleton = cls()  
  8.         return cls.__singleton  
  9.   
  10. class Test(Singleton) :  
  11.     def test(self):  
  12.         print self.__class__,id(self)  
  13.   
  14. class Test1(Test):  
  15.     def test1(self):  
  16.         print self.__class__,id(self), 'Test1'  
  17.   
  18. class Test2(Singleton):  
  19.     def test2(self):  
  20.         print self.__class__,id(self), 'Test2'  
  21.   
  22. if __name__== '__main__' :  
  23.     t1 = Test.getSingleton()  
  24.     t2 = Test.getSingleton()  
  25.   
  26.     t1.test()  
  27.     t2.test()  
  28.     assert(isinstance(t1,Test))  
  29.     assert(isinstance(t2,Test))  
  30.     assert(id(t1)== id(t2) )  
  31.   
  32.     t1 = Test1.getSingleton()  
  33.     t2 = Test1.getSingleton()  
  34.     assert(isinstance(t1,Test1))  
  35.     assert(isinstance(t2,Test1))  
  36.     assert(id(t1)== id( t2))  
  37.     t1.test()  
  38.     t1.test1()  
  39.     t2.test()  
  40.     t2.test1()  
  41.   
  42.     t1 = Test2.getSingleton()  
  43.     t2 = Test2.getSingleton()  
  44.     assert(isinstance (t1,Test2))  
  45.     assert(isinstance(t2,Test2))  
  46.     assert(id(t1)== id(t2))  
  47.     t1.test2()  
  48.     t2.test2()  
上面代碼的執行結果: 
 

從運行結果可以看出,我們可以控制同一個子類的生成同一個對象實例,但是如果Singleton類被繼承(不論是子類之間還是,子類的子類)不能控制生成一個實例。這個問題後面再探討。 

- 利用 __new__ 
提到 __new__ 就不能不說 __init__,先說說關於 __new__ 和 __init__ 的不同與用法: 
* object.__new__(cls[, ...]):調用創建 cls 類的一個新的實例。是靜態方法不用聲明。返回一個新對象的實例
* object.__init__(self[, ...]):當實例創建的時候調用。沒有返回值。
__new__ 在 __init__ 這個之前被調用:如果__new__返回一個cls的實例,那麼新的實例的__init__方法就會被調用,且self是這個新的實例。如果是自定義重寫__new__,沒有調用__init__的話__init__就不起作用了; 如果__new__不返回一個cls的實例,那麼新的實例的__init__方法就不會被調用。

示例代碼如下: 
  1. #!/usr/bin/env python  
  2. class Singleton( object ):  
  3.     def __new__(cls):  
  4.         print("Singleton __new__ being called")  
  5.         if not hasattr(cls, '_instance' ):  
  6.             cls._instance = object.__new__(cls)  
  7.         return cls._instance  
  8.     def __init__(self):  
  9.         print("Singletop __init__ being called")  
  10.   
  11. class MyClass1(Singleton):  
  12.     a = 1  
  13.     def __init__(self):  
  14.         print("MyClass1 __init__ being called")  
  15.   
  16. one = MyClass1()  
  17. two = MyClass1()  
  18.   
  19. two.a = 3  
  20. print 'one.a=' ,one.a  
  21.   
  22. assert(isinstance(one,MyClass1))  
  23. assert(isinstance(two,MyClass1))  
  24. print one.__class__,id(one)  
  25. print two.__class__,id(two)  
  26. print one == two  
  27. print one is two  
  28.   
  29. class MyClass2(Singleton):  
  30.     a = 2  
  31.     def __init__(self):  
  32.         print("MyClass2 __init__ being called")  
  33.   
  34. three = MyClass2()  
  35. three.a = 4  
  36. print 'three.a=' ,three.a  
  37. assert(isinstance(three,MyClass2))  
  38. print three.__class__,id(three)  
如上代碼,我們重寫了__new__ 方法,這段代碼返回的結果與第一種方法類似如下,也沒有解決多繼承多對象的問題。 
 

- 利用元類 __metaclass__ 
利用元類編寫單例其實原理和重寫 __new__ 是一樣的,都是在對象創建的時候進行攔截。範例代碼如下: 
  1. #!/usr/bin/env python  
  2. class Singleton(type) :  
  3.     def __init__(cls, name, bases, dict):  
  4.         print("Singleton __init__ being called")  
  5.         super(Singleton, cls).__init__(name, bases, dict)  
  6.         cls._instance = None  
  7.   
  8.     def __call__(cls):  
  9.         print("Singleton __call__ being called")  
  10.         if cls._instance is None:  
  11.             cls._instance = super(Singleton, cls).__call__()  
  12.         return cls._instance  
  13.   
  14. class MyClass1(object):  
  15.     __metaclass__ = Singleton  
  16.     a = 1  
  17.   
  18.     def __init__(self):  
  19.         print("MyClass1 __init__ being called")  
  20.   
  21. one = MyClass1()  
  22. two = MyClass1()  
  23.   
  24. two.a = 3  
  25. print 'one.a=' ,one.a  
  26.   
  27. assert(isinstance(one,MyClass1))  
  28. assert(isinstance(two,MyClass1))  
  29. print one.__class__,id(one)  
  30. print two.__class__,id(two)  
  31. print one == two  
  32. print one is two  
  33.   
  34. class MyClass2(object):  
  35.     __metaclass__ = Singleton  
  36.     a = 2  
  37.     def __init__(self):  
  38.         print("MyClass2 __init__ being called")  
  39.   
  40. three = MyClass2()  
  41. three.a = 4  
  42. print 'three.a=' ,three.a  
  43. assert(isinstance(three,MyClass2))  
  44. print three.__class__,id(three)  
Singleton 類在聲明是繼承了 type,對於 type 她其實是Python在背後用來創建所有類的元類。 
  1. class MyClass1(object):   
  2.       __metaclass__ = Singleton  
在聲明 MyClass1 時用到了以上的方式,原理是這樣的,MyClass1中有 __metaclass__ 這個屬性嗎?如果有,Python 會在內存中通過 __metaclass__ 創建一個名字為 MyClass1 的類對象。如果 Python 沒有找到 __metaclass__,它會繼續在object(父類)中尋找 __metaclass__ 屬性,並嘗試做和前面同樣的操作。如果Python 在任何父類中都找不到 __metaclass__,它就會在模塊層次中去尋找 __metaclass__,並嘗試做同樣的操作。如果還是找不到 __metaclass__ , Python 就會用內置的 type 來創建這個類對象。這里當程序發現 MyClass1 中有 __metaclass__,所以用 Singleton 類代替元類 type 創建這個類。 

其中還用到了 __call__object.__call__(self[, args...]) 當把一個實例當作方法來調用的時候,形如 instance(arg1,args2,...),那麼實際上調用的就是 instance.__call__(arg1 ,arg2,...),實際上 __call__ 模擬了 () 調用,作用在實例上,因此 __init__ 作用完了,才調用 __call__. 關於元類的具體解析請參考 這裡

- 利用 python Decorator Library——Singleton 
python 提供了豐富的裝飾者庫,其中就有現成的Singleton,官方參考鏈接參考 這裡. 這邊改寫了一個較簡單版本: 
  1. #!/usr/bin/env python  
  2. def singleton(cls):  
  3.     ''' Use class as singleton. '''  
  4.     def singleton_new():  
  5.         it = cls.__dict__.get ('__it__')  
  6.         if it is not None:  
  7.             return it  
  8.   
  9.         cls.__it__= cls()  
  10.         return cls.__it__  
  11.   
  12.     return singleton_new  
  13.   
  14.   
  15. @singleton  
  16. class Foo:  
  17.     a = 1  
  18.   
  19. one = Foo()  
  20. two = Foo()  
  21. two.a = 3  
  22. print 'one.a= ' ,one.a  
  23.   
  24. print one.__class__,id(one)  
  25. print two.__class__,id(two)  
  26. print one == two  
  27. print one is two  
  28.   
  29. @singleton  
  30. class Foo2:  
  31.     a = 1  
  32.   
  33. three = Foo2()  
  34. three.a= 4  
  35. print 'three.a= ' ,three.a  
  36. print three.__class__,id(three)  
總結:利用上面多種方法實現後,能實現對於一個類只有一個對象,但是不能避免的事類有繼承,有多個子類就可以生成多個子類的對象。其實在 python 中要實現單例模式並不需要藉用類的概念(java和C#需要類是因為所有代碼需要寫在類中),而是可以藉助模塊來實現,python 的模塊本身就是唯一的單例的,其中屬性和方法直接寫為全局的變量和方法即可。 

6. Prototype 模式  
原型模式:用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。原型模式與工廠模式一樣都生成一個對象,區別就是工廠模式是創建新的對象,而原型模式是克隆一個已經存在的對象,所以在對像初始化操作比較複雜的情況下,很實用,它能大大降低耗時,提高性能,因為“不用重新初始化對象,而是動態地獲得對象運行時的狀態”。 

先來看看,原型模式的UML: 
客戶(Client)角色:客戶類提出創建對象的請求,讓一個原型克隆自身從而創建一個新的對象。
抽象原型(Prototype)角色:此角色給出所有的具體原型類所需的接口。
具體原型(Concrete Prototype)角色:被複製的對象。此角色需要實現抽象原型角色所要求的接口.

對於 python 實現原型模式有現成的 copy 模塊 可用。 
* copy.copy(x): Return a shallow copy of x.
* copy.deepcopy(x): Return a deep copy of x.

淺拷貝和深拷貝之間的區別僅適用於復合對象(包含其他對像也就是子對象,如 list 類或實例對象): 
* Shallow copy ——構建一個新的對象然後插入到原來的引用上。只拷貝父對象,不會拷貝對象的內部的子對象。
* Deep copy ——構造一個新的對像以遞歸的形式,然後插入複製到它原來的對像上。拷貝對象及其子對象

下面為範例代碼: 
  1. #!/usr/bin/env python  
  2. import copy  
  3.   
  4.   
  5. class ICloneable:  
  6.     def shallowClone(self, name):  
  7.         obj = copy.copy(self)  
  8.         print("Test of shallowcopy: %s/%s" % (id(self), id(obj)))  
  9.         obj.name = name  
  10.         return obj  
  11.   
  12.     def deepClone(self, name):  
  13.         obj = Resume(name)  
  14.         obj.sex = self.sex  
  15.         obj.age = self.age  
  16.         obj.work.companys = []  
  17.         for company in self.work.companys:  
  18.             obj.work.companys.append(copy.deepcopy(company))  
  19.         print("Test of deepcopy: %s/%s" % (id(self), id(obj)))  
  20.         obj.name = name  
  21.         return obj  
  22.   
  23. class Company:  
  24.     workData = ""  
  25.     name = ""  
  26.     phone = ""  
  27.   
  28.     def __init__(self, workData, name, phone):  
  29.         print("Company __init__ being called")  
  30.         self.name = name  
  31.         self.phone = phone  
  32.         self.workData = workData  
  33.   
  34. class WorkExperience:  
  35.     companys = []  
  36.   
  37.     def __init__(self):  
  38.         print("WorkExperience __init__ being called")  
  39.   
  40. class Resume(ICloneable):  
  41.     name = ""  
  42.     sex = ""  
  43.     age = 0  
  44.     work = None  
  45.   
  46.     def __init__(self, name):  
  47.         print("Resume __init__ being called")  
  48.         self.name = name  
  49.         self.work = WorkExperience()  
  50.   
  51.     def setPersonInfo(self, sex, age):  
  52.         self.sex = sex  
  53.         self.age = age  
  54.   
  55.     def setWorkExperience(self, workData, company_name, company_phone):  
  56.         company = Company(workData, company_name, company_phone)  
  57.         self.work.companys.append(company)  
  58.   
  59.     def addCompany(self, name, phone):  
  60.         company = Company(name, phone)  
  61.         self.work.companys.append(company)  
  62.   
  63.     def display(self):  
  64.         print('%s, %s, %d|%s' % (self.name,self.sex,self.age, id(self)))  
  65.         for c in self.work.companys:  
  66.             print("%s:" % (c.workData))  
  67.             print("\t%s (%s)|%s" % (c.name, c.phone, id(c)))  
  68.         print('')  
  69.   
  70. def client():  
  71.     a = Resume('Tom')  
  72.     a.setPersonInfo( 'm' , 29 )  
  73.     a.setWorkExperience( "1998-2000" , "ABC.COM""09123" )  
  74.   
  75.     b = a.shallowClone('Mary')  
  76.     b.setPersonInfo( 'f'18)  
  77.     b.setWorkExperience("2000-2006" , "QQ.COM""098" )  
  78.   
  79.     c = a.deepClone('John')  
  80.     c.setPersonInfo('m'36)  
  81.     c.setWorkExperience("2006-2009" , "360.COM""765" )  
  82.   
  83.     a.display()  
  84.     b.display()  
  85.     c.display()  
  86.     return  
  87.   
  88. if __name__ == '__main__' :  
  89.     client()  
執行結果: 
 

從結果可以看出,當 b 是 的淺拷貝,那麼 b 中的實例對象 WorkExperience 只會復制了 a 中的引用,當不論是 ab 哪一個修改都會改變 a 和 b 的 WorkExperience 實例; c 是 a 的深拷貝,創建了新的 WorkExperience 實例,所以 c 只會改變自己的WorkExperience


Supplement 
[OO 設計模式] Gossip@DesignPattern : Creational - Prototype Pattern (原型模式)

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...