设计模式 with Python 7:适配器模式

在本系列的前几篇文章中我提到过,设计模式事实上是编程领域的前辈为了解决某一类问题总结出来的通用解决方案,而编程这项工作其实本身也是为了用计算机语言来描述和解决某些现实问题,所以设计模式面对的某一类问题及相应解决方案其实也可以在现实中找到对应的实体。

就像我们这篇文章要说的适配器模式,适配器在现实中其实并不少见。从中学物理会提到的电源适配器(实质上是变压器,将220V交流电转换为直流电)到“水货”电子产品必备的适配插头(一般叫旅行用万能转换插头)。

万能转换插头大概长这个样子:

前者的目的是为了将发电厂输送到用户的交流电转换为小家电可以接受的直流电而存在的,后者是为了在出国旅行(或者买了水货产品)的时候让本来不能直接使用当地的插座(不同地区的插座标准和电压标准不同)能接上而存在的。

所以说这并不是计算机领域生造的一个概念,在现实世界中早就存在,适配器的用途是在不改变某一个或多个产品的前提下,通过使用适配器作为“中间媒介”来进行“适配”,从而让多个本来规格、接口不同而不能直接相连的产品可以进行连接,就像其名称中的“适配”,其主要功能就是“适配”。

适配器

如果稍微了解一下二战历史,或者航母发展史应该会知道,航母的早期和雏形阶段是不存在专门设计和建造的航空母舰的,大多都是商船进行改造,以进行飞机起飞和回收测试,直到日本开工建造了第一艘专门的航空母舰(好像是凤翔号?),但其实直到二战结束,将现有军舰改造为航母的事也是屡见不鲜,不说日本中途岛惨败后为了补充航母紧急改建的一批,美帝那边也是用巡洋舰的舰体一口气造了80多艘护航航母。

我们现在来研究一下如何改造航母。

这和“如何将大象放进冰箱”的问题一样,并不困难,无非就是拆除舰炮,铺设甲板,再加上二战时候那个旋翼老爷机的皮糙血厚随便折腾的特性,还不是分分钟的事。

CruiserCarrier抽象基类都很简单,这里不做展示,关键的适配器CarrierAdapter的代码如下:

#######################################################
#
# CarrierAdapter.py
# Python implementation of the Class CarrierAdapter
# Generated by Enterprise Architect
# Created on:      30-6��-2021 15:38:36
# Original author: 70748
#
#######################################################
from carrier_v1.src.Cruiser import Cruiser
from carrier_v1.src.Carrier import Carrierclass CarrierAdapter(Carrier):def __init__(self, cruiser: Cruiser) -> None:super().__init__()self.__cruiser = cruiserdef recoveryAircraft(self):print("{!s} recovering aircraft".format(self.__cruiser))def takeOffAircraft(self):print("{!s} taking off aircraft".format(self.__cruiser))

这里的关键在于其构造函数接收了一个Cruiser实例作为引用,并且利用这个引用实现了Carrier的相关抽象方法。利用这个“航母-巡洋舰”适配器我们就可以轻松地将Cruiser的子类“改造”为Carrier

from .Cruiser import Cruiserclass ClevelandCruiser(Cruiser):def gunFire(self):print("{!r} starting gun fire".format(self))def __repr__(self) -> str:return self.__class__.__name__def __str__(self) -> str:return self.__class__.__name__

这是一个“克利夫兰级”巡洋舰(事实上二战中正是该级别巡洋舰改建为了独立级护航航母),我们将其改建为航母:

import os
import sys
parentDir = os.path.dirname(__file__)+"\\.."
sys.path.append(parentDir)
from carrier_v1.src.ClevelandCruiser import ClevelandCruiser
from carrier_v1.src.CarrierAdapter import CarrierAdapter
clevelandCruiser = ClevelandCruiser()
independenceCarrier = CarrierAdapter(clevelandCruiser)
independenceCarrier.takeOffAircraft()
independenceCarrier.recoveryAircraft()
clevelandCruiser.gunFire()
# ClevelandCruiser taking off aircraft
# ClevelandCruiser recovering aircraft
# ClevelandCruiser starting gun fire

可以看到,通过independenceCarrier = CarrierAdapter(clevelandCruiser)我们利用适配器将克利夫兰级巡洋舰改装为了独立级航母,并且改建的航母可以像正常航母那样释放和回收飞机。

当然和现实中不同的是在我们的代码世界原有的巡洋舰依然存在,并且可以正常进行舰炮开火:clevelandCruiser.gunFire(),这与其说是改建战舰,不如说是高达世界里的某种外挂增强装甲,可以随时剥离装甲使用原有机体进行战斗。

无论如何,这里展示的在没有改变原有产品(CarrierCruiser)的情况下,通过创建适配器(CarrierAdapter)讲原有产品结合了起来,并可以进行使用。

上面示例的完整代码见Github仓库carrier_v1

适配器模式的标准UML如下:

下面对UML中的类进行分别说明:

  • Client代表使用适配器模式的客户端程序,在上面的示例中就是一段测试用代码。
  • Target表示需要适配为的最终产品形态,因为依赖倒置原则,以及Target要用于约束适配器的对外“接口”,所以Target一般会是抽象基类或者接口。
  • Adaptee表示被适配的对象的类,可以是具体的类或者抽象类,视情况而定。
  • Adapter表示适配器类,主要用于将一个Adaptee对象“适配”为Target

类适配器

如果有人了解苏联海军的航母发展的话,就会知道在上世纪70年代左右苏联红海军发展航母的时候非常拧巴,一方面是面对北大西洋北约的围堵局面,迫切需要一款能够远洋作战帮助夺取制空权,或者至少提供舰队放空作用的军舰,另一方面苏联中央一直认为航空母舰是一种资本主义国家的进攻性武器而不予批准建造,结果就是苏联相关舰船设计局“创新性”提出了一种载机巡洋舰的设计。

长这个样子:

虽然说现在看起来很是歪门邪道,但是其实是满足苏联的突破战略的,要知道苏联在当时是无论如何都无法和美帝在海上打一场持久的海空争夺战的,但是凭借以载机巡洋舰为核心的舰队,可以在提供有限防空的同时向对面的舰队倾斜日炙或花岗岩重型反舰导弹,进行饱和打击,这是唯一的出路。

当然,红色帝国也不是没有考虑正经的航母,就算是嘴上说着“资本主义的邪恶进攻武器”,但是身体还是挺诚实的,比如那艘永远不会建成的乌里扬诺夫号核动力航母。

扯的有点远,我们分析一下苏联特色的载机巡洋舰,这级军舰同时具有导弹巡洋舰和航母的特点,既可以发射重型反舰导弹,也可以起降雅克系列垂直舰载机,虽然可以说啥都行,但啥都不行,反舰作战不如俄海军现在的彼得大帝等核动力巡洋舰,舰载机不如搭载米格或苏霍伊系列的后来的瓦良格级,但有总比没有强,至少当时的红海军是有了这么一型战舰不是。

我们现在从适配器模式的角度分析一下这款战舰的特点:

可以看到,载机巡洋舰AircraftCruiser通过从导弹巡洋舰MissileCruiser和航母Carrier多继承,从而具有了两种舰船的特点,通过这种方式创建的适配器我们称呼为类适配器,前面所说的适配器更准确的应该称之为对象适配器

这个示例相对简单,就不具体用代码实现了,感兴趣的可以自行编写。

类适配器与对象适配器不同,是通过继承而非组合实现两个或以上的不同类型的“适配”。

需要注意的是,虽然这里MissileCruiserCarrier都是抽象基类(这是考虑到直接继承自两种现成舰船不太合适),但实际使用中类适配器需要适配的必然是可以实例化的非抽象类(对抽象类适配并无实际意义)。

类适配器 VS 对象适配器

  • 类适配器通过多继承实现,如果是不支持多继承的编程语言(如Java)则无法实现,对象适配器通过组合实现,并无此限制。
  • 对象适配器可以对遵循同一抽象基类或接口的类及其子类进行适配,类适配器只能适配其派生的直接基类。
  • 对象适配器更符合多用组合少用继承的设计原则,更具有“弹性”,而类适配器不具有这些特点。
  • 对象适配器不需要知道被适配对象内部的处理逻辑,只需要利用其对外公开的方法实现所适配的目标接口。类适配器因为直接从被适配类型派生,继承了其所有处理逻辑,因此可能不需要进行任何修改即可使用,但也可能需要进行一定修改,甚至是需要处理多继承带来的某些同名方法覆盖的问题,但无论如何都需要处理并厘清基类的内部逻辑。

外观模式

有时候我们需要对一个已有的复杂系统作出封装,以方便使用。

比如Linux的各种桌面,实际上就是对Linux内核的封装,让用户通过简单的图形界面可以进行各种WIFI或者其他的系统配置修改工作。

这种思路看起来很是简单和自然,但也是一种设计模式,称之为外观模式

我们在上一篇设计模式 with Python 6:命令模式中使用一个智能家居系统作为举例,其中用SmartHomeControl表示智能家居的核心控制面板,对接入的智能家居进行汇总展示并控制,除此之外,我们完全可以单独构建一个SimpleSmartHome,实现一些更“傻瓜式”的一键功能,比如“一键看电影”:

当然,这里我们也可以用命令模式以及宏命令来实现封装,只不过这两者的封装层次不同,命令模式是将不同智能家居中端封装为了统一的操作(命令),然后可以用宏命令来组装“组合命令”,而外观模式相对更简单,只是在所有智能家居之上创建了一个可以“简易操作”的统一外部接口。

  • 这里的Raspberry指的是一个流行的微电脑“树莓派”,目前我就是在用它上边安装的媒体中心应用kodi来观看直播和电影。
  • 树莓派使用的是Linux发行版系统,Service指代其上运行的服务进程。

大部分代码都很简单,这里仅展示作为外观模式的SimpleSmartHome

#######################################################
#
# SimpleSmartHome.py
# Python implementation of the Class SimpleSmartHome
# Generated by Enterprise Architect
# Created on:      01-7��-2021 11:07:50
# Original author: 70748
#
#######################################################
from simple_smart_home.src.AirConditioner import AirConditioner
from .Light import Light
from .SmartTV import SmartTV
from .Raspberry import Raspberryclass SimpleSmartHome:def watchMovie(self):conditioner = AirConditioner()conditioner.on()conditioner.setTemprature(18)ligt = Light()ligt.setLightness(10)tv = SmartTV()tv.on()rasp = Raspberry()rasp.start()rasp.startService("Kodi")rasp.exec("Kodi", "playFavorateMovie")

完整代码见Github仓库simple_smart_home

最少知识原则

事实上,在上边的Simple_smart_home中,其实是可以通过rasp.startService("Kodi")返回一个具体服务,然后直接在SimpleSmartHome中进行调用kodi.playFavorateMovie(),亦或者可以直接使用rasp.startService("Kodi").playFavorateMovie()的写法。

但是这样一来,SimpleSmartHome将与Kodi这个具体服务产生依赖关系。

在设计模式中,存在一个最少知识原则

这个原则说的是,当前类或者函数,只应当调用其持有的引用、传入的参数、在局部创建的变量的相应方法。

这个原则的反面就是像上面那样rasp.startService("Kodi").playFavorateMovie(),这种调用了局部变量方法返回值的方法的方式是违反该原则的,造成了可以避免的多余依赖。

当然该原则并非是强制性的,所有的设计模式原则都是非强制性的,它们只代表了一般性的最优方式,而并非所有情况。

就像上面的最少知识原则,要实现该原则往往不得不创建额外的代码(如Raspberry中的exec()方法)来避免方法的级联调用。所以我们需要评估使用该原则带来的好处和为了该原则将增加的额外代码量的坏处。

总结

总的来说,适配器模式是一个相当简单的模式,而且功能非常单一。但是它和之前介绍过的装饰器模式颇为类似,所以这里对这两种模式以及外观模式进行总结。

  • 装饰器模式的目的在于在不改变“外观”(统一的抽象类、接口)的情况下增强其能力。
  • 适配器模式往往需要为了将原本不能协同工作的两个或以上类型进行“适配”,所以主要关注于如何在不同的抽象类、接口之间进行转换。
  • 外观模式的目的则是为了对于复杂的系统提供一个简化易用的接口,从而提供一个统一的易用的抽象层。

关于适配器模式的内容介绍完毕,谢谢阅读。

设计模式 with Python 7:适配器模式相关推荐

  1. 【Python】《大话设计模式》Python版代码实现

    <大话设计模式>Python版代码实现 上一周把<大话设计模式>看完了,对面向对象技术有了新的理解,对于一个在C下写代码比较多.偶尔会用到一些脚本语言写脚本的人来说,很是开阔眼 ...

  2. 浅谈设计模式及python实现

    设计模式及Python实现 设计模式是什么? Christopher Alexander:"每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心.这样你就能一次又一次 ...

  3. 《大话设计模式》Python版代码实现

    <大话设计模式>Python版代码实现 上一周把<大话设计模式>看完了,对面向对象技术有了新的理解,对于一个在C下写代码比较多.偶尔会用到一些脚本语言写脚本的人来说,很是开阔眼 ...

  4. 设计模式(三)--适配器模式

    设计模式(三)–适配器模式 文章目录 设计模式(三)--适配器模式 其他链接 1. 适配器模式 1.1 介绍 1.2 类适配器 1.3对象适配器 1.4 对比 其他链接 JVM学习笔记(一) JVM学 ...

  5. 老王讲设计模式(八)——适配器模式

    适配器模式,是作为两个不兼容的接口之间的桥梁.这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能. 公司的发展速度很快,一不小心,就收购了一个创业团队去做细分市场.这么一个改变,对于公司来说 ...

  6. 设计模式(七)——适配器模式

    设计模式(七)--适配器模式 概述 类适配器模式 对象适配器模式 应用场景 概述 我们在给手机充电的时候,一般会有一个数据线和充电头,然后插在插排上.我们是没有办法将数据线直接插在插排上的,所以我们需 ...

  7. JAVA设计模式——第 8 章 适配器模式【Adapter Pattern】(转)

    好,请安静,后排聊天的同学别吵醒前排睡觉的同学了,大家要相互理解嘛.今天讲适配器模式,这个模式也很简单,你笔记本上的那个拖在外面的黑盒子就是个适配器,一般你在中国能用,在日本也能用,虽然两个国家的的电 ...

  8. c语言 适配器模式例子,NodeJS设计模式总结【单例模式,适配器模式,装饰模式,观察者模式】...

    NodeJS设计模式总结[单例模式,适配器模式,装饰模式,观察者模式] 发布时间:2020-08-21 03:08:03 来源:脚本之家 阅读:117 作者:lucky芬 本文实例讲述了NodeJS设 ...

  9. 设计模式 with Python 10:状态模式

    设计模式 with Python 10:状态模式 如果你接触过UML的状态图,应该会对状态图或者状态机有所了解,我们今天讨论的状态模式就是这种设计的落地方案. 和之前的讲解一样,我们从一个具体案例&q ...

最新文章

  1. C语言面试题-这些简单的你能很快的写出来吗?
  2. Paxos分布式一致性算法简介和Apache ZooKeeper的概念映射
  3. 关于Android Service真正的完全详解,你需要知道的一切
  4. JVM逃逸分析(同步省略、标量替换、栈上分配)
  5. 【读书笔记】iOS-设计简单的Frenzic式益智游戏
  6. mysql order by if()或order by in()条件排序
  7. Chrome如何离线安装crx文件
  8. matlab出错及解决办法,Linux下使用Matlab符号函数出错的解决办法
  9. 系统更新后mysql用不了中文,Mysql在debian系统中不能插入中文的终极解决方案
  10. VEGAS Movie Studio 15 Platinum渲染选什么格式好?
  11. 工作中windows客户端常见问题
  12. ListView的item监听事件,并且把值传递给另一个activity
  13. 在ST官网下载STM32单片机标准固件库
  14. Word论文排版教程
  15. java 图片渐变消失_透明背景图像与渐变
  16. IDEA插件系列(105):IDEA Mind Map插件——IDEA思维导图
  17. 全面赋能,OCR文字识别2022年多场景落地应用
  18. Macbook启动台图标顺序混乱
  19. BZOJ 1123: [POI2008]BLO
  20. UML建模(活动图状态图)

热门文章

  1. Ubuntu下mount cifs
  2. office2021 版本2203现已适配win11界面风格
  3. 计算机网络智能化在铁路通信的发展,关于接入网技术在铁路通信中的应用
  4. 高职c语言技能试题,高职上机C语言试卷A.doc
  5. 中国近代史-蒋廷黻-笔记-第一章-剿夷与抚夷-第四节-民族丧失二十年的光阴
  6. R语言使用vtreat包的designTreatmentsC函数构建数据处理计划(treatment plan)、使用vtreat包进行数据准备
  7. 碱基序列的最长公共子串(Finding a Shared Motif)
  8. java环境变量设置 java_home
  9. 三星笔记本电脑光驱改SSD
  10. html 微信语音聊天,layaBOX实时语音聊天与微信登陆接口分享