适配器模式
结构型设计模式处理一个系统中不同实体之间的关系,关注的是提供一种简单的对象组合方式来创造新功能。适配器模式(Adapter pattern)实现两个不兼容接口之间的兼容。
解释一下不兼容接口的真正含义。如果把一个老组件用于一个新系统中, 或把一个新组件用于一个老系统中,不对代码进行任何修改两者就能够通信的情况很少见。但又并非总是能修改代码,或因为无法访问这些代码(例如,组件以外部库的方式提供),或因为修改代码本身就不切实际。在这些情况下,可编写一个额外的代码层,该代码层包含让两个接口之间能够通信需要进行的所有修改。这个代码层就叫适配器。
假设一个电子商务系统中包含一个calculate_total(order)函数。这个函数计算一个订单的总金额,但货币单位为DKK。顾客让支持更多的流行货币,比如USD和EUR。
- 若拥有系统的源代码,那么可以扩展系统,方法是添加一些新函数,将金额从DKK转换成USD,或者从DKK转换成EUR。
- 但是若应用仅以外部库的方式提供,无法访问其源代码,那又该怎么办呢?仍可以使用这个外部库(例如,调用它的方法),但无法修改/扩展它。解决方案是编写一个包装器(又名适配器)将数据从给定的DKK格式转换成期望的USD或EUR格式。
适配器模式并不仅对数据转换有用。若使用一个接口,期望它是 function_a(),但仅有function_b()可用,可使用一个适配器把function_b()转换 (适配)成function_a()。不仅对于函数可如此,对于函数参数也可如此。eg:有一个函数要求参数x、y、z,但你只有一个带参数x、y的函数。
某个产品制造出来之后,需要应对新的需求之时,如果希望其仍然有效,则可以使用适配器模式。通常两个不兼容接口中的一个是他方的或者是老旧的。
- 一个接口是他方的,就意味着无法访问其源代码。
- 如果是老旧的,对其重构通常是不切实际的。
- 更进一步,可以说修改一个老旧组件的实现以满足需求,不仅是不切实际的,而且也违反了开放/封闭原则。开放/封闭原则是面向对象设计的基本原则之一(SOLID中的O),声明一个软件实体应该对扩展是开放的,对修改则是封闭的。本质上这意味着应该无需修改一个软件实体的源代码就能扩展其行为。适配器模式遵从开放/封闭原则。
因此,在某个产品制造出来之后,需要应对新的需求之时,如果希望其仍然有效,使用适配器是一种更好的方式,原因如下所示:
- 不要求访问他方接口的源代码
- 不违反开放/封闭原则
实践
使用Python实现适配器设计模式有多种方法。Bruce Eckel演示的所有技巧都是使用继承,但是Python提供了一种替代方案,这种实现适配器的方式更地道一些。它使用了类的内部字典(工厂模式中已经应用了这个技巧)。
# 一个Computer类,用来显示一台计算机的基本信息
class Computer(object):
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def execute(self):
return "compute execute cmd"
class Synthesizer(object):
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def play(self):
return "synthesizer play"
class Human(object):
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def speak(self):
return "human speak"
# 客户端仅知道如何调用execute()方法,并不知道play()和speak()。在不改变Synthesizer和Human类的前提下,如何做才能让代码有效?适配器
# 通用的Adapter类,将一些带不同接口的对象适配到一个统一接口中。
class Adapter(object):
# obj参数是要适配的对象,adapted_methods是一个字典,键是客户端要调用的方法,值是应该被调用的方法。
def __init__(self, obj, adapted_method):
self.obj = obj
self.__dict__.update(adapted_method)
def __str__(self):
return str(self.obj)
def main():
# 列表objects容纳着所有对象。属于Computer类的可兼 容对象不需要适配。可以直接将它们添加到列表中。不兼容的对象则不能直接添加。
adapter_obj_list = [Computer("com1")]
synthesizer = Synthesizer("player")
adapter_obj_list.append(Adapter(synthesizer, {"execute": synthesizer.play}))
human = Human("woman")
adapter_obj_list.append(Adapter(human, {"execute": human.speak}))
# 对于所有对象,客户端代码都可以始终调用已知的execute()方法,而 无需关心被使用的类之间的任何接口差别
for obj in adapter_obj_list:
print(obj.execute())
print(obj)
if __name__ == '__main__':
main()
输出
compute execute cmd
com1
synthesizer play
player
human speak
woman
看到了如何使用适配器模式,无需修改不兼容模型的源代码就能获得接口的一致性。这是通过让一个通用的适配器类完成相关工作而实现的。
虽然在Python中可以沿袭传统方式使用子类(继承)来实现适配器模式,但这种技术是一种很棒的替代方案。
修饰器模式
无论何时我们想对一个对象添加额外的功能,都有下面这些不同的可选方法。
- 如果合理,可以直接将功能添加到对象所属的类(例如,添加一个新的方法)
- 使用组合
- 使用继承
与继承相比,通常应该优先选择组合,因为继承使得代码更难复用,继承关系是静态的,并且应用于整个类以及这个类的所有实例。
设计模式提供第四种可选方法,以支持动态地(运行时)扩展一个对象的功能,就是修饰器。修饰器(Decorator)模式能够以透明的方式(不会影响其他对象)动态地将功能添加到一个对象中。