1. 程式人生 > 實用技巧 >建立類設計模式

建立類設計模式

單例模式

一、匯流排

匯流排是計算機各種功能部件或者裝置之間傳送資料、控制訊號等資訊的公共通訊解決方案之一。現假設有如下場景:某中央處理器(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、某些程式語言中,拷貝會影響到靜態變數和靜態函式的使用。