Python装饰器模式

标签: python|设计模式|装饰器模式


引子

对于装饰器模式我正在一点一点的理解........
使用对象组合的方式,做到在运行时装饰对象,使用这种方式,将能够在不修改任何底层代码的情况下,给对象赋予新的职责

想当然的方法

以星巴兹咖啡举例子,星巴兹有几种固定种类的咖啡,种类及价格见下表

种类 价格
HouseBlend 1.99
DarkRoast 1.79
Decaf 2.99
Espreso 1.39

他们共同继承自一个Beverage的抽象类

Python装饰器模式_第1张图片
UML1.PNG-22.8kB

这种时候,可以使用下面的代码将他处理的很好,毕竟也就是几种咖啡罢了

#父类
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装饰器模式_第2张图片
UML2.PNG-35.6kB
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装饰器模式_第3张图片
2016-02-24_191915.png-57.5kB

回过头来看这些代码,好像变多了,但是解决了之前那个问题,如果有新的调料加入,不需要更改任何代码,只要增加就可以了,实现了扩展但不改变。

想来个大杯或者小杯怎么办

大杯和小杯的调料不能一个价格,当然我希望如此,那么接下来怎么改进代码,其实只要在基类里面增加一个跟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有一个装饰器功能,作用和这个模式是一样的,用这种方法实现应该更直接一些,但我还不会。

你可能感兴趣的:(Python装饰器模式)