生活中的适配器模式——身高不够鞋来凑

【故事剧情】

晚上九点半,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赶忙往车门方向走,一不小心额头碰到了把手上,只好一手护着头往外跑。两个小姑娘相似一笑~

生活中的适配器模式——身高不够鞋来凑_第1张图片

用程序来模拟生活

身材苗条、长像出众是每个人梦寐以求的,尤其是女孩子!但很多人却因为先天的原因并不能如意,这时就需要通过服装、化妆去弥补。所谓美女,三分靠长相七分靠打扮!比如身高不够,就可以通过穿高跟鞋让你看起来显得高一点;如果你本身就比较高,那穿不穿高跟鞋就没那么重要了。这里高跟鞋就起着一个适配的作用,能让你的形象增高四五厘米,下面我们就用代码来模拟一下高跟鞋在生活中的场景吧!

源码示例:

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)是两个木构件上所采用的一种凹凸结合的连接方式。凸出部分叫榫(或榫头);凹进部分叫卯(或榫眼、榫槽)。它是古代中国建筑、家具及其它木制器械的主要结构方式。

榫卯结构的经典模型如下图:
生活中的适配器模式——身高不够鞋来凑_第2张图片

榫卯是藏在木头里的灵魂!而随着时代的变化,其结构也发生着一些变化,现在很多建材生产商也在发明和生产新型的具有榫卯结构的木板。假设木板生产商有下面两块木板,木板A是榫,木板B是卯,A、B两块木板就完全吻合。他们之间的榫卯接口是一种T字形的接口。
生活中的适配器模式——身高不够鞋来凑_第3张图片

后来,随着业务的拓展,木板厂商增加了一种新木板C。但C是L形的接口,不能与木板A对接。为了让木板C能与木板A进行对接,就需要增加一个衔接板D进行适配,而这个D就相当于适配器。如下图:
生活中的适配器模式——身高不够鞋来凑_第4张图片

适配器模式通常用于对已有的系统拓展新功能时,尤其适用于在设计良好的系统框架下接入第三方的接口或第三方的SDK时。在系统的最初设计阶段,最好不要把适配器模式考虑进去,除非一些特殊的场景(如你的系统本身就是要去对接和适配多种类型的硬件接口)。

适配器模式的模型抽象

类图

适配器模式的类图如下:
生活中的适配器模式——身高不够鞋来凑_第5张图片
Target是一个接口类,是提供给用户调用的接口抽象,如上面示例中的IHightPerson。Adaptee是你要进行适配的对象类,如上面的ShortPerson。Adapter是一个适配器,是对Adaptee的适配,它将Adaptee的对象转换(或说包装)成符合Target接口的对象;如上面的DecoratePerson,将ShortPerson的getRealHeight和getShoesHeight方法包装成IHightPerson的getHeight接口。

模型说明

设计要点

适配器模式中主要三个角色,在设计适配器模式时要找到并区分这些角色:
1. 目标(Target): 即你期望的目标接口,要转换成的接口。
2. 源对象(Adaptee): 即要被转换的角色,要把谁转换成目标角色。
3. 适配器(Adapter): 适配器模式的核心角色,负责把源对象转换和包装成目标对象。

优缺点

适配器模式的优点:
  1. 可以让两个没有关联的类一起运行,起着中间转换的作用。
  2. 提高了类的复用。
  3. 灵活性好,不会破坏原有的系统。
适配器模式的缺点:
  1. 如果原有系统没有设计好(如Target不是抽象类或接口,而一个实体类),适配器模式将很难实现。
  2. 过多地使用适配器,容易使代码结构混乱,如明明看到调用的是 A 接口,内部调用的却是B接口的实现。

实战应用

有一个电子书阅读器的项目(Reader),研发之初,产品经理经过各方讨论,最后告诉我们只支持TXT和Epub格式的电子书。然后你经过仔细思考、精心设计,采用了如下图1的代码架构。在这个类图中,有一个阅读器的核心类Reader,一个TXT文档的关键类TxtBook(负责TXT格式文件的解析),和一个Epub文档的关键类EpubBook(负责Epub格式文件的解析)。
生活中的适配器模式——身高不够鞋来凑_第6张图片
图1:阅读器类图

产品上线半年后,市场响应良好,业务部门反映:有很多办公人员也在用我们的阅读器,他们希望这个阅读器能同时支持PDF格式,这样就不用在多个阅读器神之间来回切换了。这个时候我们的程序就需要增加对PDF格式的支持,而PDF并不是核心业务,我们不会单独为其开发一套PDF解析内核,而会使用一些开源的PDF库(我们称它为第三方库),如MuPDF、TCPDF等。而开源库的接口和我们的接口并不相同(如下图2),返回的内容也不是我们直接需要的,需要经过一些转换才能符合我们的要求。
生活中的适配器模式——身高不够鞋来凑_第7张图片
图2:第三方PDF解析库的类图

这时,我们就需要对PDF的解析库MuPDF进行适配。经过上面的学习,你一定知道这时该用适配器模式了,于是我们有了如下图3的类图结构。
生活中的适配器模式——身高不够鞋来凑_第8张图片
图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 文件

应用场景

  1. 系统需要使用现有的类,而这些类的接口不符合现有系统的要求。
  2. 对已有的系统拓展新功能时,尤其适用于在设计良好的系统框架下增加接入第三方的接口或第三方的SDK时。


更多更有趣的文章

想获得更多更有趣的设计模式吗?一起来阅读以下系列文章吧!

程序源码

https://github.com/luoweifu/PyDesignPattern

引导篇

生活中的设计模式——启程之前,请不要错过我【试读】

基础篇

生活中的监听模式——一坑爹的热水器

生活中的适配模式——身高不够鞋来凑

生活中的状态模式——人有少、壮、老, 水之冰、液、汽

生活中的单例模式——你是我生命的唯一

生活中的职责模式——我的假条去哪了

生活中的中介模式——找房子问中介

生活中的代理模式——帮我拿一下快递

生活中的装饰模式——你想怎么穿就怎么穿

生活中的工厂模式——你要拿铁还是摩卡

生活中的迭代模式——下一个就是你了

生活中的组合模式——自己电脑组装,价格再降三折

生活中的构建模式——你想要一辆车还是一座房

生活中的克隆模式——给你一个分身术

生活中的策略模式——怎么来不重要,人到就行

生活中的命令模式——大闸蟹,走起!

生活中的备忘模式——好记性不如烂笔头

生活中的享元模式——颜料很贵必须充分利用

生活中的外观模式——学妹别慌,学长帮你

生活中的访问模式——一千个读者一千个哈姆雷特

生活中的设计模式——与经典23种设计模式的不解渊源

生活中的设计模式——那些未完待续的设计模式

进阶篇

深入解读过滤器模式——制作一杯鲜纯细腻的豆浆

深入解读对象池技术——共享让生活更便捷

深入解读回调机制——把你技能亮出来

经验篇

谈谈我对设计模式的理解

谈谈我对设计原则的思考

谈谈我对项目重构的看法



关注【SunLogging】
这里写图片描述
长按或扫码二维码,在手机端阅读更多内容

你可能感兴趣的:(生活中的设计模式,编程思想,Python,从生活中领悟设计模式)