01月01, 2017

通俗 Python 设计模式——适配器模式

从这篇开始,介绍书中第二种类型的设计模式——结构型模式。结构型模式主要用于处理系统中不同实体间的关系,如 话题与评论 这类场景。今天介绍其中的适配器模式

适配器模式,通俗的说就是设计 接口/api,以保证程序符合 开放/封闭 原则,保持新老代码间的兼容性。

适配器模式在实际开发中的使用非常频繁,通常在系统的不同层次间,以及为系统添加扩展功能等场景时使用。因为通常情况下,原系统的代码要么无法获取——如库等、要么难以冒险重构——如运行5年以上的老旧系统牵一发而动全身。在设计中使用适配器模式,可以保证在不修改原系统代码的前提下,实现新需求与原系统的对接。

Python 实现适配器模式有多种方式,这里使用书中提供的示例作为介绍。

假设场景:

存在一套旧系统,里面包含 HumanSynthesizer 类,如下:

class Synthesizer:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return 'the {} synthesizer'.format(self.name)

    def play(self):
        return 'is playing an electronic song'

class Human:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return '{} the human'.format(self.name)

    def speak(self):
        return 'says hello'

现在新增 Computer 类如下:

class Computer:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return 'the {} computer'.format(self.name)

    def execute(self):
        return 'executes a program'

并且对于扩展系统来说,所有动作函数均使用 Obj.execute() 来执行。即对于调用者来说,原系统的 Synthesizer.play()Human.speak() 是不存在的,必须像调用 Computer.execute() 一样使用 Synthesizer.execute()Human.execute() 来调用原系统中对象的执行函数。

这就是我们之前提到的场景,无法修改原系统函数,此时新系统就可以采用适配器模式进行设计。

我们可以创建一个 Adapter 类专门用于统一接口,代码如下。

class Adapter:
    def __init__(self, obj, adapted_methods):
        self.obj = obj
        self.__dict__.update(adapted_methods)

    def __str__(self):
        return str(self.obj)

简单来说,这里是使用了 Python 的一个特殊语法糖 class.__dict__ 属性,即类的内部字典。这个特殊的属性是一个字典,存放了这个类所包含的所以属性,包括方法。所以这里将传入的类进行处理,将需要被适配器处理的方法添加到内部字典中,生成一个属于这个新适配器对象的方法。

接下来,只需要在调用时,对原有系统的类进行封装,即可实现统一使用 execute() 方法执行动作了。代码如下.

def main():
    objects = [Computer('Asus')]
    synth = Synthesizer('moog')
    objects.append(Adapter(synth, dict(execute=synth.play)))
    human = Human('Bob')
    objects.append(Adapter(human, dict(execute=human.speak)))

    for i in objects:
        print('{} {}'.format(str(i), i.execute()))

简单解释一下,就是在实例化对象后,使用 Adapter 再将对象包裹一次,最终的调用其实都是调用了 Adapter 类的对象。这里我们修改一下 main() 函数,在循环体添加一条打印对象类型的语句:

def new_main():
    objects = [Computer('Asus')]
    synth = Synthesizer('moog')
    objects.append(Adapter(synth, dict(execute=synth.play)))
    human = Human('Bob')
    objects.append(Adapter(human, dict(execute=human.speak)))

    for i in objects:
        print('{} {}'.format(str(i), i.execute()))
        print('type is {}'.format(type(i)))

执行结果如下:

the Asus computer executes a program
type is <class '__main__.Computer'>
the moog synthesizer is playing an electronic song
type is <class '__main__.Adapter'>
Bob the human says hello
type is <class '__main__.Adapter'>

可以看到,扩展系统实现的 Computer 类的对象因为不需要使用适配器处理,所以被识别为 Computer 类,但是经过 Adapter 处理后的 synthhuman 对象,已经不能被识别为 SynthesizerHuman 类,而是被识别为 Adapter 类了。如此一来,就实现了统一接口的目标。

从上面的例子可以看到,适配器模式在需要进行增量式开发的系统中是非常有用的,可以在不修改旧系统的前提下,保证代码的一致性。在合适的场景下有效的提高了代码的健壮性和兼容性。


其实还有一种实现方案,在 这里 可以看到。

本文链接:http://pycode.cc/post/easy-python-design-pattern-adapter-design-pattern.html

-- EOF --

Comments