python设计模式之适配器模式

结构型设计模式一个系统中不同实体(比如,类和对象)之间的关系,关注的是提供一种简单的对象组合方式来创造功能。

适配器模式( Adapter pattern)是一种结构型设计模式,帮助我们实现两个不兼容接口之间的容。首先,解释一下不兼容接口的真正含义。如果我们希望把一个老组件用于一个新系统中,或者把一个新组件用于一个老系统中,不对代码进行任何修改两者就能够通信的情况很少见。但又并非总是能修改代码,或因为我们无法访问这些代码(例如,组件以外部库的方式提供),或因为修改代码本身就不切实际。在这些情况下,我们可以编写一个额外的代码层,该代码层包含让两个接口之间能够通信需要进行的所有修改。这个代码层就叫适配器。

1. 现实生活真的例子

如果你有一部智能手机或者一台平板电脑,在想把它(比如, iPhone手机的闪电接口)连接到你的电脑时,就需要使用一个USB适配器。

2. 软件的例子

Grok是一个Python框架,运行在Zope 3之上,专注于敏捷开发。 Grok框架使用适配器,让已有对象无需变更就能符合指定API的标准 。

Python第三方包Traits也使用了适配器模式,将没有实现某个指定接口(或一组接口)的对象转换成实现了接口的对象 。

3. 应用案例

在某个产品制造出来之后,需要应对新的需求之时,如果希望其仍然有效,则可以使用适配器模式。通常两个不兼容接口中的一个是他方的或者是老旧的。如果一个接口是他方的,就意味着我们无法访问其源代码。如果是老旧的,那么对其重构通常是不切实际的。更进一步,我们可以说修改一个老旧组件的实现以满足我们的需求,不仅是不切实际的,而且也违反了开放/封闭原则。 开放/封闭原则( open/closeprinciple)是面向对象设计的基本原则之一,声明一个软件实体应该对扩展是开放的,对修改则是封闭的。本质上这意味着我们应该无需修改一个软件实体的源代码就能扩展其行为。适配器模式遵从开放/封闭原则 。

因此,在某个产品制造出来之后,需要应对新的需求之时,如果希望其仍然有效,使用适配器是一种更好的方式,原因如下所示。

  • [ ] 不要求访问其他地方接口的代码
  • [ ] 不违反开放/关闭原则
4. 实现

我们的应用有一个Computer类,用来显示一台计算机的基本信息。这一例子中的所有类,包括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'

在这里, execute方法是计算机可以执行的主要动作。这一方法由客户端代码调用 。

在Synthesizer类中,主要动作由play()方法执行。在Human类中,主要动作由speak()方法执行。为表明这两个类是外部的,将它们放在一个单独的模块中,如下所示。

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'

客户端仅知道如何调用execute()方法,并不知道play()和speak()。在不改变Synthesizer和Human类的前提下,我们该如何做才能让代码有效?适配器是救星!我们创建一个通用的Adapter类,将一些带不同接口的对象适配到一个统一接口中。init()方法的obj参数是我们想要适配的对象, adapted_methods是一个字典,键值对中的键是客户端要调用的方法,值是应该被调用的方法。

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

下面看看使用适配器模式的方法。列表objects容纳着所有对象。属于Computer类的可兼容对象不需要适配。可以直接将它们添加到列表中。不兼容的对象则不能直接添加。使用Adapter类来适配它们。结果是,对于所有对象,客户端代码都可以始终调用已知的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()))

现在来看看适配器模式例子的完整代码 。

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'
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'
class Adapter:
def __init__(self, obj, adapted_methods):
self.obj = obj
self.__dict__.update(adapted_methods)
def __str__(self):
return str(self.obj)
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()))
if __name__ == "__main__":
main()

输出如下:

the Asus computer executes a program
the moog synthesizer is playing an electronic song
Bob the human says hello

我们设法使得Human和Synthesizer类与客户端所期望的接口兼容,且无需改变它们的源代码。

5. 小结

我们使用适配器模式让两个(或多个)不兼容接口兼容 。无需修改不兼容模型的源代码就能获得接口的一致性。这是通过让一个通用的适配器类完成相关工作而实现的。

最新文章

  1. Hibernate jpa 在实体类中对于时间的注解
  2. hdu5879 Cure
  3. css配合js模拟的select下拉框
  4. Android RecyclerView Adapter 新式用法之SortedListAdapterCallback
  5. webapi 上传图片
  6. Unity 2DSprite
  7. Android 多状态按钮 ToggleButton
  8. 如何为linux释放内存和缓存
  9. 02-01官网静默模式安装WebLogic
  10. Menu-多级菜单
  11. 从0移植uboot(三) _编译最小可用uboot
  12. Gym - 101201E:Enclosure (点到凸包的切线)
  13. 【剑指offer】数组中出现次数超过一半的数字
  14. spring @Value注解#和$区别
  15. Mybatis通过GNDL语法引用静态常量或者枚举类型
  16. 第一篇:初识ASP.NET控件开发_第三节:“生死有序”的控件生命周期
  17. KEIL C51代码优化详细分析
  18. 图解Git命令【转】
  19. mormot解析天气预报JSON数据
  20. 软件工程第四周作业-PSP

热门文章

  1. 高效C++:资源管理
  2. git push到远程新分支
  3. [redis] -- 过期策略篇
  4. JS常用知识点(一)
  5. github 新功能 profile README.md
  6. Android仿支付宝高顶部功能条伸缩动画
  7. JAVA基础1(语法)
  8. 《Python编程初学者指南》高清PDF版|百度网盘免费下载|Python基础
  9. HTML中div嵌套div的margin不起作用
  10. 10-Pandas之数据融合(pd.merge()、df.join()、df.combine_first()详解)