专业介绍:
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
通俗介绍:
其实就是在现有组件基础上做适配工作 。使用者习惯了/一直/只能调用某一个类A来完成所需要的大部分功能,忽然有个新功能是类A不具备的,但是类B具备,而使用者又因为各种原因(降低使用者的复杂度,或成本问题)不想直接调用类B。如果能直接通过类A使用新功能是最好的。
举例:有的计算机无法直接读取SD卡,需要一个读卡器做转换,就是因为现有接口不兼容。这其中的逻辑抽离出来就是:两个对象的现有接口不兼容,但又想使用对方的功能,而强行兼容会产生较大的成本,并且已经有低成本的适配方案,所以选择适配方案是最合理的。
适配器模式包含如下角色:
- Target:目标抽象类,客户端期待调用的抽象接口(类)
- Adaptee:适配者类,客户端期待调用的真实接口(类)
- Adapter:适配器类,客户端直接调用的接口(类),用来继承或引用适配者类,从而完成兼容性工作。
下面的代码用适配器模式解决上面的例子。
代码:
# 适配器模式
import abc, six
# step-1:定义一个target目标抽象接口
@six.add_metaclass(abc.ABCMeta)
class SDCardTarget:
@abc.abstractmethod
def read_bytes(self, size, from_head=False) -> bytes:
pass
@abc.abstractmethod
def write_bytes(self, data) -> bool:
pass
# step-2: 定义一个外来的Adaptee真实接口,实现Target
class KingtonSDCard(SDCardTarget):
_capacity = 8 * 1024 * 1024 # MB
_data = bytearray(_capacity * bytearray(' ', 'utf8')) # 申请一个长度为8MB的字节数组
_curr_read_seek = 0
_curr_write_seek = 0
def read_bytes(self, size, from_head=False):
"""简易的读取SD卡实现"""
if from_head:
self._curr_read_seek = size
return bytes(self._data[:size].rstrip(b' ')) # 返回字节串 b''
if self._capacity <= (size + self._curr_read_seek):
self._curr_read_seek = self._capacity
return bytes(self._data[self._curr_seek:].rstrip(b' '))
self._curr_read_seek += size
return bytes(self._data[self._curr_read_seek:self._curr_read_seek + size].rstrip(b' '))
def write_bytes(self, data: bytes):
"""简易的写SD卡实现"""
if self._curr_write_seek + len(data) >= self._capacity:
raise Exception('Insufficient space!')
add_len = len(bytearray(data))
self._data[self._curr_write_seek:self._curr_write_seek + add_len] = bytearray(data)
self._curr_write_seek += add_len
return True
# step-3: 定义一个Adapter适配器类,继承Adaptee
# 这个类是客户端(使用者)一直在用的,比上面的类先存在
class UniformDeviceOperationAPI:
class MP3:
pass
class MP4:
pass
interface_map = {
'mp3': MP3, # 现有类已经支持的接口
'mp4': MP4, # 现有类已经支持的接口
'sd_card': KingtonSDCard # 适配器要适配的接口
}
def __init__(self, device_flag):
self._dev_flag = None
self._api_instance = None
api_cls = self.interface_map.get(device_flag)
if api_cls is not None:
self._api_instance = api_cls()
self._dev_flag = device_flag
else:
raise Exception('Unsupport device!')
self._data = ''
def existed_method_read_all(self):
"""已经支持的接口"""
return self._data
def adapted_method_read_sd_bytes(self, size, from_head=False):
"""适配好的操作SD接口"""
return self._api_instance.read_bytes(size, from_head)
def adapted_method_write_sd_bytes(self, data):
"""适配好的操作SD接口"""
return self._api_instance.write_bytes(data)
if __name__ == '__main__':
# client
device_api = UniformDeviceOperationAPI('sd_card')
data = device_api.adapted_method_read_sd_bytes(size=15)
print('read data for size=15:', data)
result = device_api.adapted_method_write_sd_bytes(b"this is data!")
print('write data into sd card SUCC:', result)
data = device_api.adapted_method_read_sd_bytes(size=15, from_head=True)
print('read again data for size=15:', data)
代码中在适配器类中只引用了适配者的接口,并不是继承于它。因为现有的组件可能已经包含许多功能,比如已经继承了别的类;所以我们进行现有组件扩展时,应尽量不修改已存在的部分。
注意:适配器模式分为类结构型模式和对象结构型模式两种,代码实现的是前者,很明显,这种方式比较费力,需要把目标类的所有方法再实现一遍,和现有组件的耦合度高;而后者的实现方式就简单一些,直接在现有组件中声明一个api_instance属性,然后暴露给客户端使用即可,客户端通过这个属性直接调用目标类的方法。
备注:关于abc, six库的基本用法参考文章 简单工厂模式 & Python示例 底部的介绍,: )
欢迎留言~
参考文章: