装饰模式:动态地给一个对象添加一些额外的职责,就添加功能来说,装饰模式比生成子类更为灵活。
1. 装饰模式基本实现代码(Python版)
import abc
class Component(metaclass=abc.ABCMeta): #定义一个对象的接口,可以给这些对象动态地添加职责。
@abc.abstractmethod
def Operation(self):
pass
class ConcreteComponent(Component): # 定义一个具体对象,可以给这个对象添加一些职责。
def Operation(self):
print("具体对象的操作")
class Decorator(Component):
# 抽象装饰类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,无需知道Decorator的存在。
def __init__(self):
self._component = Component()
def SetComponent(self,component): # 设置Component
self._component = component
def Operation(self): # 重写Operation()实际执行的市Component的Operation()
if(self._component != None):
self._component.Operation()
class ConcreteDecoratorA(Decorator): # 具体的装饰对象,起到给Component添加职责的功能。
def __init__(self):
self.__addedState = None # 本类独有的功能,区别于ConcreteDecoratorB
def Operation(self):
super(ConcreteDecoratorA,self).Operation() # 先运行原Component的Operation(),再执行本类功能。
self.__addedState = "New State"
print("具体装饰对象A的操作")
class ConcreteDecoratorB(Decorator): # 具体的装饰对象,起到给Component添加职责的功能。
def Operation(self):
super(ConcreteDecoratorB,self).Operation()
self.AddedBehavior()
print("具体装饰对象A的操作")
def AddedBehavior(self): # 本类独有的功能,区别于ConcreteDecoratorA
pass
if __name__ == '__main__':
c = ConcreteComponent()
d1 = ConcreteDecoratorA()
d2 = ConcreteDecoratorB()
d1.SetComponent(c)
d2.SetComponent(d1)
d2.Operation()
装饰模式利用SetComponent来对对象进行包装。每个装饰对象的实现就与如何使用这个对象分开了,每个装饰对象只关心自己的功能,不需要关系如何被添加到对象链当中。
当只有一个ConcreteComponent类而没有抽象的Component类,那么Decorator类可以是ConcreteComponent的一个子类。同理,如果只有一个ConcreteDecorator类,那么没必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任和合并成一个类。
2.使用装饰模式和不使用装饰模式的代码区别
例:使用Python设计一个给人搭配不同服饰的系统,控制台模式下即可。
2.1 不使用装饰模式
版本一:
class Person(object):
def __init__(self, name):
self.__name = name
def WearTShits(self):
print("大T恤")
def WearBigTrouser(self):
print("垮裤")
def WearSneakers(self):
print("破球鞋")
def Show(self):
print("装扮的",self.__name)
if __name__ == '__main__':
xc = Person('小菜')
print('装扮: ')
xc.WearTShits()
xc.WearBigTrouser()
xc.WearSneakers()
xc.Show()
此版本,将人物和穿搭封装成了Person类,所有穿搭都是Person类中的方法。如果有新的装扮,则需要对Person类进行修改,违背了开放-封闭原则。
因此需要将人物和穿搭拆分开。
第二版
from abc import ABCMeta,abstractmethod
class Person(object):
def __init__(self,name):
self.__name = name
def Show(self):
print("装扮的",self.__name)
class Finery(metaclass=abc.ABCMeta):
@abstractmethod
def Show(self):
pass
class TShits(Finery):
def Show(self):
print("大T恤")
class BigTrouser(Finery):
def Show(self):
print("垮裤")
class Sneakers(Finery):
def Show(self):
print("破球鞋")
if __name__ == '__main__':
xc = Person('小菜')
print('装扮:')
dtx = TShits()
kk = BigTrouser()
pqx = Sneakers()
# 装饰过程
dtx.Show()
kk.Show()
pqx.Show()
此版本,将人物和服饰分别进行封装成Person类和Finery类。Finery类是服饰抽象类,所有具体服饰类都继承于它,如果又新的装扮,则直接添加新的具体服饰类就可以,无需修改Person类和Finery类。但是,在具体使用时,下面的代码显得十分冗余。
dtx.Show()
kk.Show()
pqx.Show()
没有将具体的穿搭过程进行封装,犹如公众场合下换衣服的感觉,没有真正体现出先穿什么再穿什么的流程。
2.2 使用装饰模式
仿照装饰模式的基本实现代码对上面的代码进行修改,
Person类相当于ConcreteComponent类,Finery类相当于Decorator类。因为只有一个ConcreteComponent类而没有抽象的Component类,所以Decorator类可以是ConcreteComponent的一个子类。
修改结果如下:
import abc
# Person类
class Person(metaclass=abc.ABCMeta):
def __init__(self, name):
self._name = name
def Show(self):
print("装扮的",self.__name)
# 服饰类
class Finery(Person):
def __init__(self):
pass
def Decorate(self, component):
self._component = component
def Show(self):
if self._component is not None:
self._component.Show()
class TShirts(Finery):
def __init__(self):
pass
def Show(self):
print('T恤衫')
self._component.Show()
class BigTrouser(Finery):
def __init__(self):
pass
def Show(self):
print('垮裤')
self._component.Show()
if __name__ == '__main__':
person = Person('小菜')
dtx = TShirts()
kk = BigTrouser()
# 装饰过程
dtx.Decorate(person)
kk.Decorate(dtx)
kk.Show()
此版本从装饰过程中能明显体会到先穿什么再穿什么的流程。这种写法有效地把类中的核心职责和装饰功能分开,而且可以去除相关重复的装饰逻辑。每个装饰对象只需要关注自己的功能,无需关注如何被添加到对象中的。
3. python中装饰器VS装饰模式
看到这里,你可能会联想到Python自带的装饰器@语法糖,那我们谈一谈Python中的装饰器和设计模式中的装饰模式的联系与区别:
设计模式中的装饰模式:是设计模式的一种。是一种编程思想,与编程语言无关。
是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
Python装饰器:是Python中的高级函数使用上的一种语法糖。
是对装饰模式的一个更宽泛的应用,不仅仅能够应用于类,也能应用于函数,类方法和类属性。
总结:
当系统需要新功能且新功能只是为了满足一些再特定情况下才会执行的特殊行为需要。可以采用装饰模式。
在Python有现成的语法糖支持装饰模式。用于常常被用于有切面需求的场景。较为经典的有插入日志、性能測试、事务处理等。
参考: