建立類設計模式
單例模式
一、匯流排
匯流排是計算機各種功能部件或者裝置之間傳送資料、控制訊號等資訊的公共通訊解決方案之一。現假設有如下場景:某中央處理器(CPU)通過某種協議匯流排與一個訊號燈相連,訊號燈有64種顏色可以設定,中央處理器上執行著三個執行緒,都可以對這個訊號燈進行控制,並且可以獨立設定該訊號燈的顏色。抽象掉協議細節(用打印表示),如何實現執行緒對訊號等的控制邏輯。
加執行緒鎖進行控制,無疑是最先想到的方法,但各個執行緒對鎖的控制,無疑加大了模組之間的耦合。下面,我們就用設計模式中的單例模式,來解決這個問題。
什麼是單例模式?單例模式是指:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。具體到此例中,匯流排物件,就是一個單例,它僅有一個例項,各個執行緒對匯流排的訪問只有一個全域性訪問點,即惟一的例項。
Python程式碼如下:
#encoding=utf8 import threading import time #這裡使用方法__new__來實現單例模式 class Singleton(object):#抽象單例 def __new__(cls, *args, **kw): if not hasattr(cls, '_instance'): orig = super(Singleton, cls) cls._instance = orig.__new__(cls, *args, **kw) return cls._instance #匯流排 class Bus(Singleton): lock = threading.RLock() def sendData(self,data): self.lock.acquire() time.sleep(3) print "Sending Signal Data...",data self.lock.release() #執行緒物件,為更加說明單例的含義,這裡將Bus物件例項化寫在了run裡 class VisitEntity(threading.Thread): my_bus="" name="" def getName(self): return self.name def setName(self, name): self.name=name def run(self): self.my_bus=Bus() self.my_bus.sendData(self.name) if __name__=="__main__": for i in range(3): print "Entity %d begin to run..."%i my_entity=VisitEntity() my_entity.setName("Entity_"+str(i)) my_entity.start()
執行結果如下:
Entity 0 begin to run...
Entity 1 begin to run...
Entity 2 begin to run...
Sending Signal Data... Entity_0
Sending Signal Data... Entity_1
Sending Signal Data... Entity_2
在程式執行過程中,三個執行緒同時執行(執行結果的前三行先很快打印出來),而後分別佔用匯流排資源(後三行每隔3秒列印一行)。雖然看上去匯流排Bus被例項化了三次,但實際上在記憶體裡只有一個例項。
二、單例模式
單例模式是所有設計模式中比較簡單的一類,其定義如下:Ensure a class has only one instance, and provide a global point of access to it.(保證某一個類只有一個例項,而且在全域性只有一個訪問點)
三、單例模式的優點和應用
單例模式的優點
1、由於單例模式要求在全域性內只有一個例項,因而可以節省比較多的記憶體空間;
2、全域性只有一個接入點,可以更好地進行資料同步控制,避免多重佔用;
3、單例可長駐記憶體,減少系統開銷。
單例模式的應用舉例
1、生成全域性惟一的序列號;
2、訪問全域性複用的惟一資源,如磁碟、匯流排等;
3、單個物件佔用的資源過多,如資料庫等;
4、系統全域性統一管理,如Windows下的Task Manager;
5、網站計數器。
四、單例模式的缺點
1、單例模式的擴充套件是比較困難的;
2、賦於了單例以太多的職責,某種程度上違反單一職責原則(六大原則後面會講到);
3、單例模式是併發協作軟體模組中需要最先完成的,因而其不利於測試;
4、單例模式在某種情況下會導致“資源瓶頸”。
工廠類相關模式
一、快餐點餐系統
想必大家一定見過類似於麥當勞自助點餐檯一類的點餐系統吧。在一個大的觸控顯示屏上,有三類可以選擇的上餐品:漢堡等主餐、小食、飲料。當我們選擇好自己需要的食物,支付完成後,訂單就生成了。下面,我們用今天的主角--工廠模式--來生成這些食物的邏輯主體。
首先,來看主餐的生成(僅以兩種漢堡為例)。
class Burger(): name="" price=0.0 def getPrice(self): return self.price def setPrice(self,price): self.price=price def getName(self): return self.name class cheeseBurger(Burger): def __init__(self): self.name="cheese burger" self.price=10.0 class spicyChickenBurger(Burger): def __init__(self): self.name="spicy chicken burger" self.price=15.0
其次,是小食。(內容基本一致)
class Snack(): name = "" price = 0.0 type = "SNACK" def getPrice(self): return self.price def setPrice(self, price): self.price = price def getName(self): return self.name class chips(Snack): def __init__(self): self.name = "chips" self.price = 6.0 class chickenWings(Snack): def __init__(self): self.name = "chicken wings" self.price = 12.0
最後,是飲料。
class Beverage(): name = "" price = 0.0 type = "BEVERAGE" def getPrice(self): return self.price def setPrice(self, price): self.price = price def getName(self): return self.name class coke(Beverage): def __init__(self): self.name = "coke" self.price = 4.0 class milk(Beverage): def __init__(self): self.name = "milk" self.price = 5.0
以上的Burger,Snack,Beverage,都可以認為是該快餐店的產品,由於只提供了抽象方法,我們把它們叫抽象產品類,而cheese burger等6個由抽象產品類衍生出的子類,叫作具體產品類。
接下來,“工廠”就要出現了。
class foodFactory(): type="" def createFood(self,foodClass): print self.type," factory produce a instance." foodIns=foodClass() return foodIns class burgerFactory(foodFactory): def __init__(self): self.type="BURGER" class snackFactory(foodFactory): def __init__(self): self.type="SNACK" class beverageFactory(foodFactory): def __init__(self): self.type="BEVERAGE"
同樣,foodFactory為抽象的工廠類,而burgerFactory,snackFactory,beverageFactory為具體的工廠類。
在業務場景中,工廠模式是如何“生產”產品的呢?
if __name__=="__main__": burger_factory=burgerFactory() snack_factorry=snackFactory() beverage_factory=beverageFactory() cheese_burger=burger_factory.createFood(cheeseBurger) print cheese_burger.getName(),cheese_burger.getPrice() chicken_wings=snack_factorry.createFood(chickenWings) print chicken_wings.getName(),chicken_wings.getPrice() coke_drink=beverage_factory.createFood(coke) print coke_drink.getName(),coke_drink.getPrice()
可見,業務中先生成了工廠,然後用工廠中的createFood方法和對應的引數直接生成產品例項。
列印結果如下:
BURGER factory produce a instance.
cheese burger 10.0
SNACK factory produce a instance.
chicken wings 12.0
BEVERAGE factory produce a instance.
coke 4.0
二、工廠模式、簡單工廠模式、抽象工廠模式
工廠模式的定義如下:定義一個用於建立物件的介面,讓子類決定例項化哪個類。工廠方法使一個類的例項化延遲到其子類。其通用類圖如下。其產品類定義產品的公共屬性和介面,工廠類定義產品例項化的“方式”。
在上述例子中,工廠在使用前必須例項化。如果,把工廠加個類方法,寫成如下形式:
class simpleFoodFactory(): @classmethod def createFood(cls,foodClass): print "Simple factory produce a instance." foodIns = foodClass() return foodIns
在場景中寫成如下形式:
spicy_chicken_burger=simpleFoodFactory.createFood(spicyChickenBurger)
這樣,省去了將工廠例項化的過程。這種模式就叫做簡單工廠模式。
還是在上述例子中,createFood方法中必須傳入foodClass才可以指定生成的food例項種類,如果,將每一個細緻的產品都建立對應的工廠(如cheeseBurger建立對應一個cheeseBurgerFactory),這樣,生成食物時,foodClass也不必指定。事實上,此時,burgerFactory就是具體食物工廠的一層抽象。這種模式,就是抽象工廠模式。
三、工廠模式的優點和應用
工廠模式、抽象工廠模式的優點:
1、工廠模式巨有非常好的封裝性,程式碼結構清晰;在抽象工廠模式中,其結構還可以隨著需要進行更深或者更淺的抽象層級調整,非常靈活;
2、遮蔽產品類,使產品的被使用業務場景和產品的功能細節可以分而開發進行,是比較典型的解耦框架。
工廠模式、抽象工廠模式的使用場景:
1、當系統例項要求比較靈活和可擴充套件時,可以考慮工廠模式或者抽象工廠模式實現。比如,
在通訊系統中,高層通訊協議會很多樣化,同時,上層協議依賴於下層協議,那麼就可以對應建立對應層級的抽象工廠,根據不同的“產品需求”去生產定製的例項。
四、工廠類模式的不足
1、工廠模式相對於直接生成例項過程要複雜一些,所以,在小專案中,可以不使用工廠模式;
2、抽象工廠模式中,產品類的擴充套件比較麻煩。畢竟,每一個工廠對應每一類產品,產品擴充套件,就意味著相應的抽象工廠也要擴充套件
建造者模式
一、快餐點餐系統
今天的例子,還是上一次談到的快餐點餐系統。只不過,今天我們從訂單的角度來構造這個系統。
最先還是有請上次的主角們:
主餐:
class Burger(): name="" price=0.0 def getPrice(self): return self.price def setPrice(self,price): self.price=price def getName(self): return self.name class cheeseBurger(Burger): def __init__(self): self.name="cheese burger" self.price=10.0 class spicyChickenBurger(Burger): def __init__(self): self.name="spicy chicken burger" self.price=15.0
小食:
class Snack(): name = "" price = 0.0 type = "SNACK" def getPrice(self): return self.price def setPrice(self, price): self.price = price def getName(self): return self.name class chips(Snack): def __init__(self): self.name = "chips" self.price = 6.0 class chickenWings(Snack): def __init__(self): self.name = "chicken wings" self.price = 12.0
飲料:
class Beverage(): name = "" price = 0.0 type = "BEVERAGE" def getPrice(self): return self.price def setPrice(self, price): self.price = price def getName(self): return self.name class coke(Beverage): def __init__(self): self.name = "coke" self.price = 4.0 class milk(Beverage): def __init__(self): self.name = "milk" self.price = 5.0
最終,我們是要建造一個訂單,因而,需要一個訂單類。假設,一個訂單,包括一份主食,一份小食,一種飲料。(省去一些異常判斷)
class order(): burger="" snack="" beverage="" def __init__(self,orderBuilder): self.burger=orderBuilder.bBurger self.snack=orderBuilder.bSnack self.beverage=orderBuilder.bBeverage def show(self): print "Burger:%s"%self.burger.getName() print "Snack:%s"%self.snack.getName() print "Beverage:%s"%self.beverage.getName()
程式碼中的orderBuilder是什麼鬼?這個orderBuilder就是建造者模式中所謂的“建造者”了,先不要問為什麼不在order類中把所有內容都填上,而非要用builder去建立。接著往下看。
orderBuilder的實現如下:
class orderBuilder(): bBurger="" bSnack="" bBeverage="" def addBurger(self,xBurger): self.bBurger=xBurger def addSnack(self,xSnack): self.bSnack=xSnack def addBeverage(self,xBeverage): self.bBeverage=xBeverage def build(self): return order(self)
在場景中如下去實現訂單的生成:
if __name__=="__main__": order_builder=orderBuilder() order_builder.addBurger(spicyChickenBurger()) order_builder.addSnack(chips()) order_builder.addBeverage(milk()) order_1=order_builder.build() order_1.show()
列印結果如下:
Burger:spicy chicken burger
Snack:chips
Beverage:milk
二、建造者模式
建造者模式的定義如下:將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
建造者模式的作用,就是將“構建”和“表示”分離,以達到解耦的作用。在上面訂單的構建過程中,如果將order直接通過引數定義好(其構建與表示沒有分離),同時在多處進行訂單生成,此時需要修改訂單內容,則需要一處處去修改,業務風險也就提高了不少。
在建造者模式中,還可以加一個Director類,用以安排已有模組的構造步驟。對於在建造者中有比較嚴格的順序要求時,該類會有比較大的用處。在上述例子中,Director實現如下:
class orderDirector(): order_builder="" def __init__(self,order_builder): self.order_builder=order_builder def createOrder(self,burger,snack,beverage): self.order_builder.addBurger(burger) self.order_builder.addSnack(snack) self.order_builder.addBeverage(beverage) return self.order_builder.build()
通過createOrder直接代入引數,即可直接生成訂單。
三、建造者模式的優點和使用場景
優點:
1、封裝性好,使用者可以不知道物件的內部構造和細節,就可以直接建造物件;
2、系統擴充套件容易;
3、建造者模式易於使用,非常靈活。在構造性的場景中很容易實現“流水線”;
4、便於控制細節。
使用場景:
1、目標物件由元件構成的場景中,很適合建造者模式。例如,在一款賽車遊戲中,車輛生成時,需要根據級別、環境等,選擇輪胎、懸掛、骨架等部件,構造一輛“賽車”;
2、在具體的場景中,物件內部介面需要根據不同的引數而呼叫順序有所不同時,可以使用建造者模式。例如:一個植物養殖器系統,對於某些不同的植物,澆水、施加肥料的順序要求可能會不同,因而可以在Director中維護一個類似於佇列的結構,在例項化時作為引數代入到具體建造者中。
四、建造者模式的缺點
1、“加工工藝”對使用者不透明。(封裝的兩面性)
原型模式
一、圖層
大家如果用過類似於Photoshop的平面設計軟體,一定都知道圖層的概念。圖層概念的提出,使得設計、圖形修改等操作更加便利。設計師既可以修改和繪製當前影象物件,又可以保留其它影象物件,邏輯清晰,且可以及時得到反饋。本節內容,將以圖層為主角,介紹原型模式。
首先,設計一個圖層物件。
class simpleLayer: background=[0,0,0,0] content="blank" def getContent(self): return self.content def getBackgroud(self): return self.background def paint(self,painting): self.content=painting def setParent(self,p): self.background[3]=p def fillBackground(self,back): self.background=back
在實際的實現中,圖層實現會很複雜,這裡僅介紹相關的設計模式,做了比較大的抽象,用background表示背景的RGBA,簡單用content表示內容,除了直接繪畫,還可以設定透明度。
新建圖層,填充藍底並畫一隻狗,可以簡單表示如下:
if __name__=="__main__": dog_layer=simpleLayer() dog_layer.paint("Dog") dog_layer.fillBackground([0,0,255,0]) print "Background:",dog_layer.getBackgroud() print "Painting:",dog_layer.getContent()
列印如下:
Background: [0, 0, 255, 0]
Painting: Dog
接下來,如果需要再生成一個同樣的圖層,再填充同樣的顏色,再畫一隻同樣狗,該如何做呢?還是按照新建圖層、填充背景、畫的順序麼?或許你已經發現了,這裡可以用複製的方法來實現,而複製(clone)這個動作,就是原型模式的精髓了。
按照此思路,在圖層類中新加入兩個方法:clone和deep_clone
from copy import copy, deepcopy class simpleLayer: background=[0,0,0,0] content="blank" def getContent(self): return self.content def getBackgroud(self): return self.background def paint(self,painting): self.content=painting def setParent(self,p): self.background[3]=p def fillBackground(self,back): self.background=back def clone(self): return copy(self) def deep_clone(self): return deepcopy(self) if __name__=="__main__": dog_layer=simpleLayer() dog_layer.paint("Dog") dog_layer.fillBackground([0,0,255,0]) print "Background:",dog_layer.getBackgroud() print "Painting:",dog_layer.getContent() another_dog_layer=dog_layer.clone() print "Background:", another_dog_layer.getBackgroud() print "Painting:", another_dog_layer.getContent()
列印結果如下:
Background: [0, 0, 255, 0]
Painting: Dog
Background: [0, 0, 255, 0]
Painting: Dog
clone和deep_clone有什麼區別呢?大多數程式語言中,都會涉及到深拷貝和淺拷貝的問題,一般來說,淺拷貝會拷貝物件內容及其內容的引用或者子物件的引用,但不會拷貝引用的內容和子物件本身;而深拷貝不僅拷貝了物件和內容的引用,也會拷貝引用的內容。所以,一般深拷貝比淺拷貝複製得更加完全,但也更佔資源(包括時間和空間資源)。舉個例子,下面的場景,可以說明深拷貝和淺拷貝的區別。
if __name__=="__main__": dog_layer=simpleLayer() dog_layer.paint("Dog") dog_layer.fillBackground([0,0,255,0]) print "Original Background:",dog_layer.getBackgroud() print "Original Painting:",dog_layer.getContent() another_dog_layer=dog_layer.clone() another_dog_layer.setParent(128) another_dog_layer.paint("Puppy") print "Original Background:", dog_layer.getBackgroud() print "Original Painting:", dog_layer.getContent() print "Copy Background:", another_dog_layer.getBackgroud() print "Copy Painting:", another_dog_layer.getContent()
列印如下:
Original Background: [0, 0, 255, 0]
Original Painting: Dog
Original Background: [0, 0, 255, 128]
Original Painting: Dog
Copy Background: [0, 0, 255, 128]
Copy Painting: Puppy
淺拷貝後,直接對拷貝後引用(這裡的陣列)進行操作,原始物件中該引用的內容也會變動。如果將another_dog_layer=dog_layer.clone()換成another_dog_layer=dog_layer.deep_clone(),即把淺拷貝換成深拷貝,其如果如下:
Original Background: [0, 0, 255, 0]
Original Painting: Dog
Original Background: [0, 0, 255, 0]
Original Painting: Dog
Copy Background: [0, 0, 255, 128]
Copy Painting: Puppy
深拷貝後,其物件內的引用內容也被進行了複製。
二、原型模式
原型模式定義如下:用原型例項指定建立物件的種類,並且通過複製這些原型建立新的物件。
需要注意一點的是,進行clone操作後,新物件的建構函式沒有被二次執行,新物件的內容是從記憶體裡直接拷貝的。
三、原型模式的優點和使用場景
優點:
1、效能極佳,直接拷貝比在記憶體裡直接新建例項節省不少的資源;
2、簡化物件建立,同時避免了建構函式的約束,不受建構函式的限制直接複製物件,是優點,也有隱患,這一點還是需要多留意一些。
使用場景:
1、物件在修改過後,需要複製多份的場景。如本例和其它一些涉及到複製、貼上的場景;
2、需要優化資源的情況。如,需要在記憶體中建立非常多的例項,可以通過原型模式來減少資源消耗。此時,原型模式與工廠模式配合起來,不管在邏輯上還是結構上,都會達到不錯的效果;
3、某些重複性的複雜工作不需要多次進行。如對於一個裝置的訪問許可權,多個物件不用各申請一遍許可權,由一個裝置申請後,通過原型模式將許可權交給可信賴的物件,既可以提升效率,又可以節約資源。
四、原型模式的缺點
1、深拷貝和淺拷貝的使用需要事先考慮周到;
2、某些程式語言中,拷貝會影響到靜態變數和靜態函式的使用。