第四章:装饰模式

2020-08-18  本文已影响0人  Benedict清水

一、什么是装饰模式

故事
来到大城市李力和女朋友分居两地,饱受相思之苦。现在终于工作稳定下来,女朋友想要趁假期来见一面李力。见面当天李力心情激动,决定好好捯饬自己一番......
下身休闲裤配休闲皮鞋,加银色针扣头的黑色腰带;上身紫红色针织毛衣,内套一件白色衬衫;头戴一副方形黑框眼镜。
我们用程序模拟剧情

from abc import ABCMeta, abstractmethod


class Person(metaclass=ABCMeta):
    """人"""

    def __init__(self, name):
        self._name = name

    @abstractmethod
    def wear(self):
        print("着装:")


class Engineer(Person):
    """工程师"""

    def __init__(self, name, skill):
        super().__init__(name)
        self.__skill = skill

    def getSkill(self):
        return self.__skill

    def wear(self):
        print("我是 " + self.getSkill() + "工程师 " + self._name, end=", ")
        super().wear()


class Teacher(Person):
    """教师"""

    def __init__(self, name, title):
        super().__init__(name)
        self.__title = title

    def getTitle(self):
        return self.__title

    def wear(self):
        print("我是 " + self._name + self.getTitle(), end=", ")
        super().wear()


class ClothingDecorator:
    """服装装饰器的基类"""

    def __init__(self, person):
        self._decorated = person

    def wear(self):
        self._decorated.wear()
        self.decorate()

    @abstractmethod
    def decorate(self):
        pass


class CasualPantDecorator(ClothingDecorator):
    """休闲裤装饰器"""

    def __init__(self, person):
        super().__init__(person)

    def decorate(self):
        print("一条卡其色休闲裤")


class BeltDecorator(ClothingDecorator):
    """腰带装饰器"""

    def __init__(self, person):
        super().__init__(person)

    def decorate(self):
        print("一条银色针扣头的黑色腰带")


class LeatherShoesDecorator(ClothingDecorator):
    """皮鞋装饰器"""

    def __init__(self, person):
        super().__init__(person)

    def decorate(self):
        print("一双深色休闲皮鞋")


class KnittedSweaterDecorator(ClothingDecorator):
    """针织毛衣装饰器"""

    def __init__(self, person):
        super().__init__(person)

    def decorate(self):
        print("一件紫红色针织毛衣")


class WhiteShirtDecorator(ClothingDecorator):
    """白色衬衣装饰器"""

    def __init__(self, person):
        super().__init__(person)

    def decorate(self):
        print("一件白色衬衣")


class GlassesDecorator(ClothingDecorator):
    """眼镜装饰器"""

    def __init__(self, person):
        super().__init__(person)

    def decorate(self):
        print("一副方形黑框眼镜")


def testDecorator():
    tony = Engineer("Tony", "python")
    pant = CasualPantDecorator(tony)
    belt = BeltDecorator(pant)
    shoes = LeatherShoesDecorator(belt)
    shirt = WhiteShirtDecorator(shoes)
    sweater = KnittedSweaterDecorator(shirt)
    glasses = GlassesDecorator(sweater)
    glasses.wear()
    print()
    decorateTeacher = GlassesDecorator(WhiteShirtDecorator(LeatherShoesDecorator(Teacher("wells", "教授"))))
    decorateTeacher.wear()


if __name__ == "__main__":
    testDecorator()

% python decorator.py

我是 python工程师 Tony, 着装:
一条卡其色休闲裤
一条银色针扣头的黑色腰带
一双深色休闲皮鞋
一件白色衬衣
一件紫红色针织毛衣
一副方形黑框眼镜

我是 wells教授, 着装:
一双深色休闲皮鞋
一件白色衬衣
一副方形黑框眼镜

上面的 decorateTeacher = GlassesDecorator(WhiteShirtDecorator(LeatherShoesDecorator(Teacher("wells", "教授"))))是将多个对象的创建过程合在一起,是一种优雅的写法。

二、装饰模式的定义

动态的给一个对象增加一些额外的职责,就扩展对象功能来说,装饰模式比生成子类的方式更为灵活。

2.1 装饰模式设计思想

在程序中,我们希望动态的给一个类增加额外的功能,而不改动原有的代码,就可以用装饰模式进行拓展。

2.2 python中的装饰器

在Python中一切都是对象:一个实例是对象,一个函数也是一个对象,甚至类本身也是一个对象。在python中,可以将一个函数作为参数传递给另一个函数,也可以将一个类作为一个参数传递给一个函数。

2.2.1 python 中函数的特殊功能

在python中,函数可以作为一个参数传递给另外一个函数,也可以在函数中返回一个函数,还可以在函数内部再定义函数。

def func(num):
    """定义内部函数并返回"""

    def firstInnerFunc():
        return "这是第一个内部函数"

    def secondInnerFunc():
        return "这是第二个内部函数"

    if num == 1:
        return firstInnerFunc
    else:
        return secondInnerFunc


print(func(1))
print(func(2))
print(func(1)())
print(func(2)())

firstFunc = func(1)
secondFunc = func(2)
print(firstFunc)
print(secondFunc)
print(firstFunc())
print(secondFunc())

输出结果:

<function func.<locals>.firstInnerFunc at 0x10b92d310>
<function func.<locals>.secondInnerFunc at 0x10b92d430>
这是第一个内部函数
这是第二个内部函数
<function func.<locals>.firstInnerFunc at 0x10b92d310>
<function func.<locals>.secondInnerFunc at 0x10b92d4c0>
这是第一个内部函数
这是第二个内部函数

上面两种调用方式是一样的结果。

2.2.2 装饰器修饰函数

装饰器的作用,包装一个函数,并改变(拓展)它的行为。
例:我们希望每一个函数在被调用之前和调用之后,记录一条日志。

import logging
logging.basicConfig(level=logging.INFO)


def loggingDecorator(func):
    """记录日志的装饰器"""

    def wrapperLogging(*args, **kwargs):
        logging.info("开始执行%s()..." % func.__name__)
        func(*args, **kwargs)
        logging.info("%s()执行完成!" % func.__name__)

    return wrapperLogging


def showInfo(*args, **kwargs):
    print("这是一个测试函数, 参数:", args, kwargs)


showInfo = loggingDecorator(showInfo)
showInfo('arg1', 'arg2', kwarg1=1, kwarg2=2)

输出结果:

INFO:root:开始执行showInfo()...
这是一个测试函数, 参数: ('arg1', 'arg2') {'kwarg1': 1, 'kwarg2': 2}
INFO:root:showInfo()执行完成!

我们在logginDecorator中定义了一个内部函数wrapperLogging,用于在传入的函数中执行前后记录日志,一般称这个函数为包装函数,并在最后返回这个函数。我们称logginDecorator为装饰器。Python也提供了更优雅的语法糖。把showInfo = loggingDecorator(showInfo)换成@loggingDecorator

import logging

logging.basicConfig(level=logging.INFO)


def loggingDecorator(func):
    """记录日志的装饰器"""

    def wrapperLogging(*args, **kwargs):
        logging.info("开始执行%s()..." % func.__name__)
        func(*args, **kwargs)
        logging.info("%s()执行完成!" % func.__name__)

    return wrapperLogging


@loggingDecorator
def showInfo(*args, **kwargs):
    print("这是一个测试函数, 参数:", args, kwargs)


showInfo('arg1', 'arg2', kwarg1=1, kwarg2=2)

输出结果:

INFO:root:开始执行showInfo()...
这是一个测试函数, 参数: ('arg1', 'arg2') {'kwarg1': 1, 'kwarg2': 2}
INFO:root:showInfo()执行完成!

升级版本:我们希望我们的日志记录等级不仅是info,也可以使debug。

def loggingDecorator(prefix):
    def loggingDecoratorfunc(func):
        """记录日志的装饰器"""
        def wrapperLogging(*args, **kwargs):
            if prefix == 'info':
                logging.info("%s开始执行%s()..." %(prefix, func.__name__))
                func(*args, **kwargs)
                logging.info("%s执行结束%s()..." %(prefix, func.__name__))
            else:
                logging.debug("%s开始执行%s()..." % (prefix, func.__name__))
                func(*args, **kwargs)
                logging.debug("%s执行结束%s()..." % (prefix, func.__name__))
        return wrapperLogging
    return loggingDecoratorfunc


@loggingDecorator('info')
def showInfo(*args, **kwargs):
    print("这是一个测试函数, 参数:", args, kwargs)


showInfo('arg1', 'arg2', kwarg1=1, kwarg2=2)

把上面的定义翻译成高阶函数的调用,就是:

showInfo = loggingDecorator('DEBUG')(showInfo)
showInfo('arg1', 'arg2', kwarg1=1, kwarg2=2)

上面的语句看上去还是比较绕,再展开一下:

log_decorator = loggingDecorator('info')
showInfo = log_decorator(showInfo)
showInfo('arg1', 'arg2', kwarg1=1, kwarg2=2)

2.2.3装饰器修饰类
装饰器可以是一个函数,也可以是一个类(必须要实现__call__)方法,使其是callable的)。同时装饰器不仅可以修改一个函数,还可以修饰一个类,示例如下:

class ClassDecorator:
    """类装饰器,记录一个类被实例化的次数"""

    def __init__(self, func):
        self.__numOfCall = 0
        self.__func = func

    def __call__(self, *args, **kwargs):
        self.__numOfCall += 1
        obj = self.__func(*args, *kwargs)
        print("创建%s的第%d个实例:%s" % (self.__func.__name__, self.__numOfCall, id(obj)))
        return obj


@ClassDecorator
class MyClass:
    def __init__(self, name):
        self.__name = name

    def getName(self):
        return self.__name


if __name__ == "__main__":
    tony = MyClass("Tony")
    karry = MyClass("Karry")
    print(id(tony))
    print(id(karry))

输出结果:

创建MyClass的第1个实例:4457689680
创建MyClass的第2个实例:4457691120
4457689680
4457691120

这里ClassDecorator是类装饰器,记录一个类被实例化的次数。其修饰一个类和修饰一个函数的用法是一样的,只需在定义类时@ClassDECORATOR即可。

三、总结

3.1装饰模式的优缺点

优点
(1) 使用装饰模式来实现扩展比使用继承更加灵活,它可以在不创造更多子类的情况下,使对象的功能加以扩展。
(2) 可以动态地给一个对象附加更多的功能。
(3) 可以用不同的装饰器进行多重装饰,装饰的顺序不同,可能产生不同的效果。
(4) 装饰类和被装饰类可以独立发展,不会相互耦合;装饰模式相当于继承的一个替代模式。
缺点
与继承相比,用装饰的方式扩展功能容易出错,拍错也更困难。

3.2 应用场景

(1)有大量独立的扩展,为支持每一种组合将产生大量的子类,使的子类数目呈爆炸性增长。
(2)需要动态地增加或撤销功能时
(3)不能采用生成子类的方法进行扩充时,类的定义不能用于生成子类。

以上为个人学习笔记:
《人人都懂设计模式》作者:罗伟富

上一篇下一篇

猜你喜欢

热点阅读