文章目录
前言
日常生活中,经常会遇到这样的情况:我们碰到了一个问题需要解决,手里现有的工具呢不能够直接解决问题,需要通过变通,比如说,水管需要延长才能达到我们的距离需求,但是呢现有的水管直径不同,这个时候就需要一个转接头能够将两个不同直径的水管拼接起来。诸如此类的问题,比比皆是,如电脑屏幕连接的数据线又各种格式,DVI、VGA、HDMI等,也经常遇到要用转接头连接一端是VGA一端是HDMI接口情况,再比如家庭用电电压是电器所需电压,并不一致,这个时候也需要转换。
那么在程序世界里,自然也会碰到类似的问题。现有的程序可以解决大部分需求问题,但是又不能直接使用!所以就需要一个转接头能够将现有的程序和所需的程序连接起来。我们将这种模式成为适配器模式,也叫Adapter模式。
模式介绍
类设计
类比现实 | 类 | |
---|---|---|
实际情况 | 大直径水管 | Banner类 |
适配器 | 转接头 | PrintBanner类 |
需求情况 | 小直径水管 | Print接口 |
类UML关系图
代码实现
本次示例中,使用一个简单的打印字符串的类,我们目前有一个现有类Banner,功能是将所给的字符串加上括号或者*号,打印到控制台。假设我们现在有一个新需求,需要对所给字符串进行弱显示和强显示,分别对应加括号和加*。而且我们不能直接使用Banner类方法,因为这两个方法属于protect级别,不对外开放。所以我们需要写一个类,继承自Banner,并将该方法开放出去。
Banner类
"""
@Time: 2023/3/18 22:54
@Auth: CivilDog
@File: Banner.py
@IDE: PyCharm
@Motto: Nothing is impossible
"""
class Banner:
"""
打印字符串的类,类比现有程序
"""
def __init__(self, str_to_print: str):
self.__str_to_print = str_to_print
def _show_with_paren(self):
print("("+self.__str_to_print+")")
def _show_with_aster(self):
print("*"+self.__str_to_print+"*")
PrintInterface类
"""
@Time: 2023/3/18 22:57
@Auth: CivilDog
@File: PrintInterface.py
@IDE: PyCharm
@Motto: Nothing is impossible
"""
import abc
from abc import ABCMeta
class PrintInterface(metaclass=ABCMeta):
"""
打印类的接口类,抽象出需求所需要的方法,并由实际使用类所继承和实现
"""
@abc.abstractmethod
def print_weak(self):
pass
@abc.abstractmethod
def print_strong(self):
pass
PrintBanner类
"""
@Time: 2023/3/18 22:58
@Auth: CivilDog
@File: PrintBanner.py
@IDE: PyCharm
@Motto: Nothing is impossible
"""
from DesignMode.AdapterMode.PrintInterface import PrintInterface
from DesignMode.AdapterMode.Banner import Banner
class PrintBanner(PrintInterface, Banner):
"""
多重继承PrintInterface和Banner,实现父类定义的抽象方法,和开放protected级别的方法
"""
def __init__(self, str_to_print: str):
super(PrintBanner, self).__init__(str_to_print)
def print_weak(self):
self._show_with_paren()
def print_strong(self):
self._show_with_aster()
AdapterClient类
"""
@Auth: CivilDog
@File: AdapterClient.py
@IDE: PyCharm
@Motto: Nothing is impossible
"""
from DesignMode.AdapterMode.PrintBanner import PrintBanner
class AdapterClient:
"""
Adapter模式的测试入口
"""
@staticmethod
def run(*args, **kwargs):
str_to_print = "Hello World!"
p_b = PrintBanner(str_to_print)
p_b.print_weak()
p_b.print_strong()
if __name__ == '__main__':
AdapterClient.run()
输出结果
(Hello World!)
*Hello World!*
Process finished with exit code 0
利用委托,避免多继承
因为多继承可能引入新的问题,而且往往难以排查。所以有时候项目里会要求尽量避免多重继承!
这个时候可以换用委托的方式进行,也就是将PrintBanner类的工作委托给其他类,也就是Banner类。这种方式也可以在不暴露Banner类本身的情况下,开放我们所需要的接口。
委托方式也是程序开发中常见的一种编程方法。
Banner类
"""
@Time: 2023/3/18 22:54
@Auth: CivilDog
@File: Banner.py
@IDE: PyCharm
@Motto: Nothing is impossible
"""
class Banner:
"""
打印字符串的类,类比现有程序
"""
def __init__(self, str_to_print: str):
self.__str_to_print = str_to_print
def show_with_paren(self):
print("("+self.__str_to_print+")")
def show_with_aster(self):
print("*"+self.__str_to_print+"*")
PrintBanner类
class PrintBanner(PrintInterface):
def __init__(self, str_to_print: str):
super(PrintBanner, self).__init__()
self.__banner = Banner(str_to_print)
def print_weak(self):
self.__banner.show_with_paren()
def print_strong(self):
self.__banner.show_with_aster()
总结
为什么要用适配器模式
以上举了一个简单的小例子,其实这里如果实现这种需求,可以直接修改Banner类,将方法暴露出来不就行了。为什么还要这么麻烦折腾出多余的两个类。
避免影响原来代码,降低成本
举例是为了让大家理解适配器模式的使用方法。实际开发情况和开发需求比这个要复杂的多。我们现有的程序一般都是经过严格测试才上线的。如果因为来了新需求,而直接在源代码基础上进行改动,那就需要重新进行测试,成本比较高,而且很容易出现问题,影响范围也比较大。
采用适配器模式,可以避免这种情况,原来的代码经过测试已经比较稳定,而且都是在线上运行的。通过适配,将现有代码使用到新项目,新需求中,即便是出现问题,也很容易判断是适配代码的问题。
只需要接口,即可实现新的类
python语言都是源码开放的,但是很多编译型语言往往都不会开源,而是提供一些API供外部调用。这个时候,在项目中,我们为了避免暴露第三方的东西,一般都会自行封装一个类。就是适配器模式。
版本升级中往往出现它的身影
软件生命周期总是伴随升级。极少情况下能够做到完全抛弃旧版本,所以新旧版本的兼容问题,往往令人头疼。适配器模式,可以帮助我们轻松的应对这种情况!
什么情况下不能使用适配器模式
现有程序和需求之间完全不匹配的时候,自然无法使用,要自行实现新的类。
《图解设计模式》结城浩