【通俗说设计模式】八、适配器模式 & Python示例

专业介绍:

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

通俗介绍: 

其实就是在现有组件基础上做适配工作 。使用者习惯了/一直/只能调用某一个类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示例 底部的介绍,: )

欢迎留言~

参考文章:

http://c.biancheng.net/view/1354.html

猜你喜欢

转载自blog.csdn.net/sc_lilei/article/details/103381711