6、Python與設計模式–裝飾器模式

途索發表於2017-02-24

一、快餐點餐系統(3)

又提到了那個快餐點餐系統,不過今天我們只以其中的一個類作為主角:飲料類。首先,回憶下飲料類:

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

除了基本配置,快餐店賣可樂時,可以選擇加冰,如果加冰的話,要在原價上加0.3元;賣牛奶時,可以選擇加糖,如果加糖的話,要原價上加0.5元。怎麼解決這樣的問題?可以選擇裝飾器模式來解決這一類的問題。首先,定義裝飾器類:

class drinkDecorator():
    def getName(self):
        pass
    def getPrice(self):
        pass

class iceDecorator(drinkDecorator):
    def __init__(self,beverage):
        self.beverage=beverage
    def getName(self):
        return self.beverage.getName()+" +ice"
    def getPrice(self):
        return self.beverage.getPrice()+0.3
    
class sugarDecorator(drinkDecorator):
    def __init__(self,beverage):
        self.beverage=beverage
    def getName(self):
        return self.beverage.getName()+" +sugar"
    def getPrice(self):
        return self.beverage.getPrice()+0.5

構建好裝飾器後,在具體的業務場景中,就可以與飲料類進行關聯。以可樂+冰為例,示例業務場景如下:

if  __name__=="__main__":
    coke_cola=coke()
    print "Name:%s"%coke_cola.getName()
    print "Price:%s"%coke_cola.getPrice()
    ice_coke=iceDecorator(coke_cola)
    print "Name:%s" % ice_coke.getName()
    print "Price:%s" % ice_coke.getPrice()

列印結果如下:
Name:coke
Price:4.0
Name:coke +ice
Price:4.3

二、裝飾器模式

裝飾器模式定義如下:動態地給一個物件新增一些額外的職責。在增加功能方面,裝飾器模式比生成子類更為靈活。
f1.png
裝飾器模式和上一節說到的代理模式非常相似,可以認為,裝飾器模式就是代理模式的一個特殊應用,兩者的共同點是都具有相同的介面,不同點是側重對主題類的過程的控制,而裝飾模式則側重對類功能的加強或減弱。
上一次說到,JAVA中的動態代理模式,是實現AOP的重要手段。而在Python中,AOP通過裝飾器模式實現更為簡潔和方便。
先來解釋一下什麼是AOP。AOP即Aspect Oriented Programming,中文翻譯為面向切面的程式設計,它的含義可以解釋為:如果幾個或更多個邏輯過程中(這類邏輯過程可能位於不同的物件,不同的介面當中),有重複的操作行為,就可以將這些行為提取出來(即形成切面),進行統一管理和維護。舉例子說,系統中需要在各個地方列印日誌,就可以將列印日誌這一操作提取出來,作為切面進行統一維護。
從程式設計思想的關係來看,可以認為AOP和OOP(物件導向的程式設計)是並列關係,二者是可以替換的,也可以結合起來用。實際上,在Python語言中,是天然支援裝飾器的,如下例:

def log(func):
    def wrapper(*args, **kw):
        print `call %s():` % func.__name__
        return func(*args, **kw)
    return wrapper

@log
def now():
    print `2016-12-04`
if  __name__=="__main__":
    now()

列印如下:
call now():
2016-12-04
log介面就是裝飾器的定義,而Python的@語法部分則直接支援裝飾器的使用。
如果要在快餐點餐系統中列印日誌,該如何進行AOP改造呢?可以藉助類的靜態方法或者類方法來實現:

class LogManager:
    @staticmethod
    def log(func):
        def wrapper(*args):
            print "Visit Func %s"%func.__name__
            return func(*args)
        return wrapper

在需要列印日誌的地方直接@LogManager.log,即可列印出訪問的日誌資訊。如,在beverage類的函式前加上@LogManager.log,場景類保持不變,則列印結果如下:
Visit Func getName
Name:coke
Visit Func getPrice
Price:4.0
Visit Func getName
Name:coke +ice
Visit Func getPrice
Price:4.3

三、裝飾器模式的優點和應用場景

優點:
1、裝飾器模式是繼承方式的一個替代方案,可以輕量級的擴充套件被裝飾物件的功能;
2、Python的裝飾器模式是實現AOP的一種方式,便於相同操作位於不同呼叫位置的統一管理。
應用場景:
1、需要擴充套件、增強或者減弱一個類的功能,如本例。

四、裝飾器模式的缺點

1、多層裝飾器的除錯和維護有比較大的困難。


相關文章