标簽: python 設計模式 裝飾器模式
引子
對于裝飾器模式我正在一點一點的了解........
使用對象組合的方式,做到在運作時裝飾對象,使用這種方式,将能夠在不修改任何底層代碼的情況下,給對象賦予新的職責
想當然的方法
以星巴茲咖啡舉例子,星巴茲有幾種固定種類的咖啡,種類及價格見下表
種類
價格
HouseBlend
1.99
DarkRoast
1.79
Decaf
2.99
Espreso
1.39
他們共同繼承自一個Beverage的抽象類
這種時候,可以使用下面的代碼将他處理的很好,畢竟也就是幾種咖啡罷了
#父類
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
當購買咖啡時,顧客要求添加各種各樣的調料,例如:奶,糖,豆漿,摩卡,奶泡........
星巴茲會根據添加調料的不同,重新計算最終的價格,是以訂單系統需要考慮到這些調料部分,這個時候怎麼辦,繼續增加類,無窮無盡的類,哪天牛奶要是價格漲價了,那我就需要将所有涉及牛奶的子類價格全部更新一遍,那這一天就是粘貼複制了
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
現在再來看看下面這張圖,應該了解的深刻一些
回過頭來看這些代碼,好像變多了,但是解決了之前那個問題,如果有新的調料加入,不需要更改任何代碼,隻要增加就可以了,實作了擴充但不改變。
想來個大杯或者小杯怎麼辦
大杯和小杯的調料不能一個價格,當然我希望如此,那麼接下來怎麼改進代碼,其實隻要在基類裡面增加一個跟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有一個裝飾器功能,作用和這個模式是一樣的,用這種方法實作應該更直接一些,但我還不會。