晚上九点半,Tony上了地铁,准备回家,正巧还有一个空位,赶紧走向前坐下。工作一天后,疲惫不堪的他正准备坐着打个盹小睡一会。这时进来两个小姑娘,一个小巧可爱,一个身姿曼妙;嬉笑地聊着天走到了Tony的前面。Tony犹豫了片刻后还是绅士般地给小女孩让了个座……
两个小姑娘道了声谢谢,便挤在一块坐下了,继续有说有笑地谈论着……
Amy:周末在商场里看到你和一个帅哥在一起。好你个Nina,脱单了也不告诉姐姐我,太不够意思了!怎么……想金屋藏”娇”啊!
Nina:不是啦,也是最近刚有的事,还没来得及告诉你呢。
Amy:那快说说呗!那小哥看着很高啊!
Nina:嗯,他1米85。
Amy:厉害了,你155他185,这就是传说中的最萌身高组合啊!
Nina:嗯,走在大街上,别人都回头看我们,弄的我挺不好了意思的~
Amy:你这是身在福中不知福啊!别人就是因为想求也求不到呢!
Nina:也有很气的时候啦,有时生气想打他,结果粉拳一出去就被他的大手包了饺子。
Amy:哈哈哈哈,还有呢!
Nina:还有一件很囧的事,我一抬头总是看到他的鼻毛,他一低头总是看到她的头发屑!
Amy:哈哈哈!笑的我肚子痛了……所以你们在一起,你一定要天天洗头,他一定要天天修鼻毛咯~
Nina:是啊!可麻烦了~
Amy:看来还是我这160的身高最棒了!衣服可以随便挑,更重要的是我男友175,穿上高跟鞋,我就可以挽着他的手肩并肩地走~
Nina:这就是所谓的身高不够鞋来凑吗?
Amy:不然怎么叫万能的高跟鞋呢……
Nina:好羡慕啊!在我这,高跟鞋也无能~
Amy:… …正听的兴起时,地铁门开了。Tony才反应过来,到站了,该下车了。Tony赶忙往车门方向走,一不小心额头碰到了把手上,只好一手护着头往外跑。两个小姑娘相似一笑~
身材苗条、长像出众是每个人梦寐以求的,尤其是女孩子!但很多人却因为先天的原因并不能如意,这时就需要通过服装、化妆去弥补。所谓美女,三分靠长相七分靠打扮!比如身高不够,就可以通过穿高跟鞋让你看起来显得高一点;如果你本身就比较高,那穿不穿高跟鞋就没那么重要了。这里高跟鞋就起着一个适配的作用,能让你的形象增高四五厘米,下面我们就用代码来模拟一下高跟鞋在生活中的场景吧!
源码示例:
from abc import ABCMeta, abstractmethod
# 引入ABCMeta和abstractmethod来定义抽象类和抽象方法
class IHightPerson(metaclass=ABCMeta):
"""接口类,提供空实现的方法,由子类去实现"""
@abstractmethod
def getName(self):
"""获取姓名"""
pass
@abstractmethod
def getHeight(self):
"""获取身高"""
pass
@abstractmethod
def appearance(self, person):
"""外貌"""
pass
class HighPerson(IHightPerson):
"""个高的人"""
def __init__(self, name):
self.__name = name
def getName(self):
return self.__name
def getHeight(self):
return 170
def appearance(self):
print(self.getName() + "身高" + str(self.getHeight()) + ",完美如你,天生的美女!")
class ShortPerson:
"""个矮的人"""
def __init__(self, name):
self.__name = name
def getName(self):
return self.__name
def getRealHeight(self):
return 160
def getShoesHeight(self):
return 6
class DecoratePerson(ShortPerson, IHightPerson):
"""有高跟鞋搭配的人"""
def __init__(self, name):
super().__init__(name)
def getName(self):
return super().getName()
def getHeight(self):
return super().getRealHeight() + super().getShoesHeight()
def appearance(self):
print(self.getName() + "身高" + str(self.getHeight()) + ", 在高跟鞋的适配下,你身高不输高圆圆,气质不输范冰冰!")
class HeightMatch:
"""身高匹配"""
def __init__(self, person):
self.__person = person
def matching(self, person1):
"""假设标准身高差为10厘米内"""
distance = abs(self.__person.getHeight() - person1.getHeight())
isMatch = distance <= 10
print(self.__person.getName() + "和" + person1.getName() + "是否为情侣的标准身高差:"
+ ("是" if isMatch else "否") + ", 差值:" + str(distance))
测试代码:
def testPerson():
lira = HighPerson("Lira")
lira.appearance()
demi = DecoratePerson("Demi");
demi.appearance()
haigerMatching = HeightMatch(HighPerson("Haiger"))
haigerMatching.matching(lira)
haigerMatching.matching(demi)
输出结果:
Lira身高170,完美如你,天生的美女!
Demi身高166, 在高跟鞋的适配下,你身高不输高圆圆,气质不输范冰冰!
Haiger和Lira是否为情侣的标准身高差:是, 差值:0
Haiger和Demi是否为情侣的标准身高差:是, 差值:4
上面的例子中,高跟鞋起着一个适配的作用, 让你的形象增高5-7厘米,完全不在话下,而且效果立竿见影!使得一些女孩原本不符合接待员的真实身高,在鞋子的帮助下也能符合条件。如高跟鞋一样,使原本不匹配某种功能的对象变得匹配这种功能,这在程序中就叫做适配器模式。
Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而无法一起工作的两个类能够在一起工作。
适配器模式的作用:
1. 接口转换,将原有的接口(或方法)转换成另一种接口;
2. 用新的接口包装一个已有的类;
3. 匹配一个老的组件到一个新的接口。
适配器模式又叫变压器模式,也叫包装模式(Wrapper),它的核心思想是将一个对象经过包装或转换后使它符合指定的接口,使得调用方可以像使用这接口的一般对象一样使用它。这一思想,在我们生活中可谓是处处可见,比如变压器插座,能让你像使用国内电器一样使用美标(110V)电器;还有就是各种转接头,如MiniDP转HDMI转接头、HDMI转VGA线转换器、Micro USB转Type-C转接头等。
你们知道吗?“设计模式”一词最初是来源于建筑领域,而中国古建筑是世界建筑史的一大奇迹(如最具代表性的紫禁城),中国古建筑的灵魂是一种叫榫卯结构的建造理念。
榫卯(sǔn mǎo)是两个木构件上所采用的一种凹凸结合的连接方式。凸出部分叫榫(或榫头);凹进部分叫卯(或榫眼、榫槽)。它是古代中国建筑、家具及其它木制器械的主要结构方式。
榫卯是藏在木头里的灵魂!而随着时代的变化,其结构也发生着一些变化,现在很多建材生产商也在发明和生产新型的具有榫卯结构的木板。假设木板生产商有下面两块木板,木板A是榫,木板B是卯,A、B两块木板就完全吻合。他们之间的榫卯接口是一种T字形的接口。
后来,随着业务的拓展,木板厂商增加了一种新木板C。但C是L形的接口,不能与木板A对接。为了让木板C能与木板A进行对接,就需要增加一个衔接板D进行适配,而这个D就相当于适配器。如下图:
适配器模式通常用于对已有的系统拓展新功能时,尤其适用于在设计良好的系统框架下接入第三方的接口或第三方的SDK时。在系统的最初设计阶段,最好不要把适配器模式考虑进去,除非一些特殊的场景(如你的系统本身就是要去对接和适配多种类型的硬件接口)。
适配器模式的类图如下:
Target是一个接口类,是提供给用户调用的接口抽象,如上面示例中的IHightPerson。Adaptee是你要进行适配的对象类,如上面的ShortPerson。Adapter是一个适配器,是对Adaptee的适配,它将Adaptee的对象转换(或说包装)成符合Target接口的对象;如上面的DecoratePerson,将ShortPerson的getRealHeight和getShoesHeight方法包装成IHightPerson的getHeight接口。
适配器模式中主要三个角色,在设计适配器模式时要找到并区分这些角色:
1. 目标(Target): 即你期望的目标接口,要转换成的接口。
2. 源对象(Adaptee): 即要被转换的角色,要把谁转换成目标角色。
3. 适配器(Adapter): 适配器模式的核心角色,负责把源对象转换和包装成目标对象。
有一个电子书阅读器的项目(Reader),研发之初,产品经理经过各方讨论,最后告诉我们只支持TXT和Epub格式的电子书。然后你经过仔细思考、精心设计,采用了如下图1的代码架构。在这个类图中,有一个阅读器的核心类Reader,一个TXT文档的关键类TxtBook(负责TXT格式文件的解析),和一个Epub文档的关键类EpubBook(负责Epub格式文件的解析)。
图1:阅读器类图
产品上线半年后,市场响应良好,业务部门反映:有很多办公人员也在用我们的阅读器,他们希望这个阅读器能同时支持PDF格式,这样就不用在多个阅读器神之间来回切换了。这个时候我们的程序就需要增加对PDF格式的支持,而PDF并不是核心业务,我们不会单独为其开发一套PDF解析内核,而会使用一些开源的PDF库(我们称它为第三方库),如MuPDF、TCPDF等。而开源库的接口和我们的接口并不相同(如下图2),返回的内容也不是我们直接需要的,需要经过一些转换才能符合我们的要求。
图2:第三方PDF解析库的类图
这时,我们就需要对PDF的解析库MuPDF进行适配。经过上面的学习,你一定知道这时该用适配器模式了,于是我们有了如下图3的类图结构。
图3:兼容PDF的类图结构
代码实现如下:
class Page:
"电子书一页的内容"
def __init__(self, pageNum):
self.__pageNum = pageNum
def getContent(self):
return "第 " + str(self.__pageNum) + " 页的内容..."
class Catalogue:
"目录结构"
def __init__(self, title):
self.__title = title
self.__chapters = []
self.setChapter("第一章")
self.setChapter("第二章")
def setChapter(self, title):
self.__chapters.append(title)
def showInfo(self):
print("标题:" + self.__title)
for chapter in self.__chapters:
print(chapter)
class IBook:
"电子书文档的接口类"
def parseFile(self, filePath):
pass
def getCatalogue(self):
pass
def getPageCount(self):
pass
def getPage(self, pageNum):
pass
class TxtBook(IBook):
"TXT解析类"
def parseFile(self, filePath):
# 模拟文档的解析
print(filePath + " 文件解析成功")
self.__pageCount = 500
return True
def getCatalogue(self):
return Catalogue("TXT电子书")
def getPageCount(self):
return self.__pageCount
def getPage(self, pageNum):
return Page(pageNum)
class EpubBook(IBook):
"TXT解析类"
def parseFile(self, filePath):
# 模拟文档的解析
print(filePath + " 文件解析成功")
self.__pageCount = 800
return True
def getCatalogue(self):
return Catalogue("Epub电子书")
def getPageCount(self):
return self.__pageCount
def getPage(self, pageNum):
return Page(pageNum)
class Outline:
"第三方PDF解析库的目录类"
pass
class PdfPage:
"PDF页"
def __init__(self, pageNum):
self.__pageNum = pageNum
def getPageNum(self):
return self.__pageNum
class ThirdPdf:
"第三方PDF解析库"
def __init__(self):
self.__pageSize = 0
def open(self, filePath):
print("第三方解析PDF文件:" + filePath)
self.__pageSize = 1000
return True
def getOutline(self):
return Outline()
def pageSize(self):
return self.__pageSize
def page(self, index):
return PdfPage(index)
class PdfAdapterBook(ThirdPdf, IBook):
"TXT解析类"
def parseFile(self, filePath):
# 模拟文档的解析
rtn = super().open(filePath)
if(rtn):
print(filePath + "文件解析成功")
return rtn
def getCatalogue(self):
outline = super().getOutline()
print("将Outline结构的目录转换成Catalogue结构的目录")
return Catalogue("PDF电子书")
def getPageCount(self):
return super().pageSize()
def getPage(self, pageNum):
page = self.page(pageNum)
print("将PdfPage的面对象转换成Page的对象")
return Page(page.getPageNum())
# 导入os库
import os
class Reader:
"阅读器"
def __init__(self, name):
self.__name = name
self.__filePath = ""
self.__curBook = None
self.__curPageNum = -1
def __initBook(self, filePath):
self.__filePath = filePath
extName = os.path.splitext(filePath)[1]
if(extName.lower() == ".epub"):
self.__curBook = EpubBook()
elif(extName.lower() == ".txt"):
self.__curBook = TxtBook()
elif(extName.lower() == ".pdf"):
self.__curBook = PdfAdapterBook()
else:
self.__curBook = None
def openFile(self, filePath):
self.__initBook(filePath)
if(self.__curBook is not None):
rtn = self.__curBook.parseFile(filePath)
if(rtn):
self.__curPageNum = 1
return rtn
return False
def closeFile(self):
print("关闭 " + self.__filePath + " 文件")
return True
def showCatalogue(self):
catalogue = self.__curBook.getCatalogue()
catalogue.showInfo()
def prePage(self):
return self.gotoPage(self.__curPageNum - 1)
def nextPage(self):
return self.gotoPage(self.__curPageNum + 1)
def gotoPage(self, pageNum):
if(pageNum < 1 or pageNum > self.__curBook.getPageCount()):
return None
self.__curPageNum = pageNum
print("显示第" + str(self.__curPageNum) + "页")
page = self.__curBook.getPage(self.__curPageNum)
page.getContent()
return page
测试代码:
def testReader():
reader = Reader("阅读器")
if(not reader.openFile("平凡的世界.txt")):
return
reader.showCatalogue()
reader.gotoPage(1)
reader.nextPage()
reader.closeFile()
print()
if (not reader.openFile("平凡的世界.epub")):
return
reader.showCatalogue()
reader.gotoPage(5)
reader.nextPage()
reader.closeFile()
print()
if (not reader.openFile("平凡的世界.pdf")):
return
reader.showCatalogue()
reader.gotoPage(10)
reader.nextPage()
reader.closeFile()
输出结果:
平凡的世界.txt 文件解析成功
标题:TXT电子书
第一章
第二章
显示第1页
显示第2页
关闭 平凡的世界.txt 文件
平凡的世界.epub 文件解析成功
标题:Epub电子书
第一章
第二章
显示第5页
显示第6页
关闭 平凡的世界.epub 文件
第三方解析PDF文件:平凡的世界.pdf
平凡的世界.pdf文件解析成功
将Outline结构的目录转换成Catalogue结构的目录
标题:PDF电子书
第一章
第二章
显示第10页
将PdfPage的面对象转换成Page的对象
显示第11页
将PdfPage的面对象转换成Page的对象
关闭 平凡的世界.pdf 文件
想获得更多更有趣的设计模式吗?一起来阅读以下系列文章吧!
https://github.com/luoweifu/PyDesignPattern
生活中的设计模式——启程之前,请不要错过我【试读】
生活中的监听模式——一坑爹的热水器
生活中的适配模式——身高不够鞋来凑
生活中的状态模式——人有少、壮、老, 水之冰、液、汽
生活中的单例模式——你是我生命的唯一
生活中的职责模式——我的假条去哪了
生活中的中介模式——找房子问中介
生活中的代理模式——帮我拿一下快递
生活中的装饰模式——你想怎么穿就怎么穿
生活中的工厂模式——你要拿铁还是摩卡
生活中的迭代模式——下一个就是你了
生活中的组合模式——自己电脑组装,价格再降三折
生活中的构建模式——你想要一辆车还是一座房
生活中的克隆模式——给你一个分身术
生活中的策略模式——怎么来不重要,人到就行
生活中的命令模式——大闸蟹,走起!
生活中的备忘模式——好记性不如烂笔头
生活中的享元模式——颜料很贵必须充分利用
生活中的外观模式——学妹别慌,学长帮你
生活中的访问模式——一千个读者一千个哈姆雷特
生活中的设计模式——与经典23种设计模式的不解渊源
生活中的设计模式——那些未完待续的设计模式
深入解读过滤器模式——制作一杯鲜纯细腻的豆浆
深入解读对象池技术——共享让生活更便捷
深入解读回调机制——把你技能亮出来
谈谈我对设计模式的理解
谈谈我对设计原则的思考
谈谈我对项目重构的看法