标签: 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有一个装饰器功能,作用和这个模式是一样的,用这种方法实现应该更直接一些,但我还不会。