天天看點

python中的裝飾器、裝飾器模式_Python裝飾器模式

标簽: python 設計模式 裝飾器模式

python中的裝飾器、裝飾器模式_Python裝飾器模式

引子

對于裝飾器模式我正在一點一點的了解........

使用對象組合的方式,做到在運作時裝飾對象,使用這種方式,将能夠在不修改任何底層代碼的情況下,給對象賦予新的職責

想當然的方法

以星巴茲咖啡舉例子,星巴茲有幾種固定種類的咖啡,種類及價格見下表

種類

價格

HouseBlend

1.99

DarkRoast

1.79

Decaf

2.99

Espreso

1.39

他們共同繼承自一個Beverage的抽象類

python中的裝飾器、裝飾器模式_Python裝飾器模式

這種時候,可以使用下面的代碼将他處理的很好,畢竟也就是幾種咖啡罷了

#父類

class Beverage(object):

def __init__(self):

self.description = "Unknown description"

def getDescription(self):

return self.description

def cost(self):

pass

#子類

class HouseBlend(Beverage):

def __init__(self):

super(HouseBlend, self).__init__()

self.description = "HouseBlend coffee"

def cost(self):

return 1.99

#子類

class DarkRoast(Beverage):

def __init__(self):

super(DarkRoast, self).__init__()

self.description = "DarkRoast coffee"

def cost(self):

return 1.79

#子類

class Decaf(Beverage):

def __init__(self):

super(Decaf, self).__init__()

self.description = "Decaf coffee"

def cost(self):

return 2.99

#子類

class Espresso(Beverage):

def __init__(self):

super(Espresso, self).__init__()

self.description = "Espresso coffee"

def cost(self):

return 1.39

#執行個體

for item in [HouseBlend(), DarkRoast(), Decaf(), Espresso()]:

print("{0}: {1}".format(item.getDescription(), item.cost()))

一共是五個類,列印的結果如下

HouseBlend coffee: 1.99

DarkRoast coffee: 1.79

Decaf coffee: 2.99

Espresso coffee: 1.39

當購買咖啡時,顧客要求添加各種各樣的調料,例如:奶,糖,豆漿,摩卡,奶泡........

星巴茲會根據添加調料的不同,重新計算最終的價格,是以訂單系統需要考慮到這些調料部分,這個時候怎麼辦,繼續增加類,無窮無盡的類,哪天牛奶要是價格漲價了,那我就需要将所有涉及牛奶的子類價格全部更新一遍,那這一天就是粘貼複制了

python中的裝飾器、裝飾器模式_Python裝飾器模式

class Beverage(object):

def __init__(self):

self.description = "Unknow description"

def getDescription(self):

return self.description

def cost(self):

pass

class HouseBlend(Beverage):

def __init__(self):

super(HouseBlend, self).__init__()

self.description = "HouseBlend coffee"

def cost(self):

return 1.99

#我需要在下面定義無窮無盡的子類

......

......

......

這樣弄下類,也許會有成百上千各類。

怎麼辦

可以将所有的調料放到父類裡面,Beverage裡面計算每種調料的價格,子類最後将自己的價格加到這些增加的調料價格上,這樣的話好像還是五個類

class Beverage(object):

#定義兩個類屬性,作為初始值

condiment = 0.0

description = ""

#定義一個dict,用來存放調料

def __init__(self, **condiments):

for k, v in condiments.items():

setattr(self, k , v)

#下面是判斷哪些調料使用了,之後加上這種調料的價格和描述

if condiments[k] == "milk":

Beverage.condiment += 0.99

Beverage.description += " add milk"

if condiments[k] == "soy":

Beverage.condiment += 0.89

Beverage.description += " add soy"

#下面可以不停的增加調料種類

def getDescription(self):

return self.description + Beverage.description

#這裡隻寫了一種咖啡種類,其餘三種格式全部一緻,這裡省略了

class HouseBlend(Beverage):

def __init__(self, **condiments):

super(HouseBlend, self).__init__(**condiments)

self.description = "HouseBlend Coffee "

#cost現在就為調料與本身的價格之和

def cost(self):

return Beverage.condiment + 1.99

#這個執行個體裡增加了milk調料,列印最終的價格和描述

hb = HouseBlend(condi1="milk", condi2="soy", condi3="soy")

print("{0}: {1}".format(hb.getDescription(), hb.cost()))

這麼設計看似解決了類爆炸的問題,但是它違反了一個原則,這個原則就是

類應該對擴充開放,對修改關閉

此處,如果有新的調料加入,就必須去對Beverage類進行修改,這樣做似乎違反了面向對象界的一些規則,但我個人覺得沒有什麼,我不是寫代碼的,不知道這個原則到了大型程式上是不是後果很嚴重,不過想想寫出的類一增加功能,就需要去原來的基礎上修改代碼,确實不利于後續維護,擴充也許是最好的選擇。

新的目标

這樣看來有了新的目标,就是将上面的代碼改成擴充的,而不是修改的,這樣隻需要每次寫一個新的調料放上去就行。

答案就是裝飾器模式,每一種調料都是一個個裝飾材料,而四種咖啡是被裝飾對象,需要增加哪種調料,就把這個調料的裝飾花環套在咖啡的脖子上,直到咖啡已經喝不了為止。

定義

動态地将責任附加到對象上,若有擴充功能,裝飾者提供了比繼承更具有彈性的替代方案

先看Beverage類,他是所有類的基類,定義了getDescription和cost方法

class Beverage(object):

def __init__(self):

self.description = "Unknown Beverage"

def getDescription(self):

return self.description

def cost(self):

pass

定義四個咖啡元件,這就是要被裝飾者裝飾的被裝飾者,在這裡定義了兩個,其餘兩個格式都是一緻的,隻要更改一下description的内容和價格即可

class HouseBlend(Beverage):

def __init__(self):

super(HouseBlend, self).__init__()

self.description = "HouseBlend"

def cost(self):

return 1.99

class Espresso(Beverage):

def __init__(self):

super(Espresso, self).__init__()

self.description = "Espresso"

def cost(self):

return 1.39

def getDescription(self):

return self.description

接下來定義裝飾者類,他們用來裝飾被裝飾者,在這裡自然就是一堆堆的調料了

#這個類是裝飾者的類的基類,其實沒有也無所謂,因為所有的類都是`Beverage`類型

class CondimentDecorator(Beverage):

def getDescription(self):

pass

#這是調料milk

class Milk(CondimentDecorator):

#這裡傳入被裝飾者,可以是咖啡元件,也可以是已經被mocha或其他調料裝飾過的咖啡元件,主要

#看裝飾者在裝飾過程中的位置

def __init__(self, beverage):

super(Milk, self).__init__()

self.beverage = beverage

def getDescription(self):

return self.beverage.getDescription() + ", Milk"

def cost(self):

return self.beverage.cost() + .2

#這是調料mocha

class Mocha(CondimentDecorator):

#這裡傳入被裝飾者,可以是咖啡元件,也可以是已經被milk或其他調料裝飾過的咖啡元件,主要

#看裝飾者在裝飾過程中的位置

def __init__(self, beverage):

super(Mocha, self).__init__()

self.beverage = beverage

#這裡自然是之前的元件描述加上這種調料的描述

def getDescription(self):

return self.beverage.getDescription() + ", Mocha"

#那這裡自然也就是之前的元件的價格和這種調料的價格了

def cost(self):

return self.beverage.cost() + .3

#這後面你可以增加無窮無盡的調料

實作

#先建立一個咖啡元件的執行個體

beverage = HouseBlend()

#先被milk裝飾一遍

beverage = Milk(beverage)

#再被mocha裝飾一遍

beverage = Mocha(beverage)

#後面可以接着寫,被其他别的調料接着裝飾

print("{0}: {1}".format(beverage.getDescription(), "%.3f" % beverage.cost()))

輸出看看

HouseBlend, Milk, Mocha: 2.490

現在再來看看下面這張圖,應該了解的深刻一些

python中的裝飾器、裝飾器模式_Python裝飾器模式

回過頭來看這些代碼,好像變多了,但是解決了之前那個問題,如果有新的調料加入,不需要更改任何代碼,隻要增加就可以了,實作了擴充但不改變。

想來個大杯或者小杯怎麼辦

大杯和小杯的調料不能一個價格,當然我希望如此,那麼接下來怎麼改進代碼,其實隻要在基類裡面增加一個跟size有關系的方法就行

class Beverage(object):

def __init__(self):

self.description = "Unknown Beverage"

def getDescription(self):

return self.description

def cost(self):

pass

def getSize(self):

return self.size

#設定一下杯子尺寸

def setSize(self, cupSize):

self.size = cupSize

之後将裝飾者和被裝飾者裡面cost方法上增加一些判斷即可,畢竟對價格産生直接影響

被裝飾者

class HouseBlend(Beverage):

def __init__(self):

super(HouseBlend, self).__init__()

self.description = "HouseBlend"

def cost(self):

if self.getSize() == "TALL":

return 1.99

elif self.getSize() == "GRANDE":

return 2.99

elif self.getSize() == "VENTI":

return 3.99

裝飾者

class Milk(CondimentDecorator):

def __init__(self, beverage):

super(Milk, self).__init__()

self.beverage = beverage

def getDescription(self):

return self.beverage.getDescription() + ", Milk"

def cost(self):

if self.beverage.getSize() == "TALL":

return self.beverage.cost() + .2

elif self.beverage.getSize() == "GRANDE":

return self.beverage.cost() + .25

elif self.beverage.getSize() == "VENTI":

return self.beverage.cost() + .30

結尾

python有一個裝飾器功能,作用和這個模式是一樣的,用這種方法實作應該更直接一些,但我還不會。