定义
- 将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而无法一起工作的两个类能够在一起工作。
- 适配模式的作用:
- 接口转换,将原有的接口(或方法)转换成另一种接口。
- 用新的接口包装一个已有的类。
- 匹配一个老的组件到一个新的接口。
- 又叫变压器模式,也叫包装模式(Wrapper)
适配模式的模型抽象
代码框架
from abc import ABCMeta, abstractmethod
# 引入ABCMeta和abstractmethod来定义抽象类和抽象方法
class Target(metaclass=ABCMeta):
"""目标类"""
@abstractmethod
def function(self):
pass
class Adaptee:
"""源对象类"""
def speciaficFunction(self):
print("被适配对象的特殊功能")
class Adapter(Target):
"""适配器"""
def __init__(self, adaptee):
self.__adaptee = adaptee
def function(self):
print("进行功能的转换")
self.__adaptee.speciaficFunction()
类图
- 适配模式的实现有两种方式:一种是组合方式,另一种是继承方式
- 适配模式的两种实现方式,比较推荐组合的方式,因为在一些没有 interface 类型的编程语言(如 C++、Python)中,Adapter 类就会多继承,同时继承 Target 和 Adaptee,在程序设计中应该尽量避免多继承(虽然 Target 只是一个接口类)。
模型说明
- 适配模式中主要有三个角色,在设计适配模式时要找到并区分这些角色。
- 目标(Target):即你期望的目标接口,要转换成的接口。
- 源对象(Adaptee):即要被转换的角色,要把谁转换成目标角色。
- 适配器(Adapter):适配模式的核心角色,负责把源对象转换和包装成目标对象。
- 优缺点
- 优点:
- 可以让两个没有关联的类一起运行,起中间转换的作用。
- 提高了类的复用率。
- 灵活性好,不会破坏原有系统。
- 缺点:
- 如果原有系统没有设计好(如 Target 不是抽象类或接口,而是一个实体类),适配模式将很难实现。
- 过多地使用适配器,容易使代码结构混乱,如明明看到调用的是 A 接口,内部调用的却是 B 接口的实现。
例子
from abc import ABCMeta, abstractmethod
# 引入ABCMeta和abstractmethod来定义抽象类和抽象方法
import os
# 导入os库,用于文件、路径相关的解析
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 = []
def addChapter(self, title):
self.__chapters.append(title)
def showInfo(self):
print("书名:" + self.__title)
print("目录:")
for chapter in self.__chapters:
print(" " + chapter)
class IBook(metaclass=ABCMeta):
"""电子书文档的接口类"""
@abstractmethod
def parseFile(self, filePath):
"""解析文档"""
pass
@abstractmethod
def getCatalogue(self):
"""获取目录"""
pass
@abstractmethod
def getPageCount(self):
"""获取页数"""
pass
@abstractmethod
def getPage(self, pageNum):
"""获取第pageNum页的内容"""
pass
class TxtBook(IBook):
"""TXT解析类"""
def parseFile(self, filePath):
# 模拟文档的解析
print(filePath + " 文件解析成功")
self.__title = os.path.splitext(filePath)[0]
self.__pageCount = 500
return True
def getCatalogue(self):
catalogue = Catalogue(self.__title)
catalogue.addChapter("第一章 标题")
catalogue.addChapter("第二章 标题")
return catalogue
def getPageCount(self):
return self.__pageCount
def getPage(self, pageNum):
return Page(pageNum)
class EpubBook(IBook):
"""Epub解析类"""
def parseFile(self, filePath):
# 模拟文档的解析
print(filePath + " 文件解析成功")
self.__title = os.path.splitext(filePath)[0]
self.__pageCount = 800
return True
def getCatalogue(self):
catalogue = Catalogue(self.__title)
catalogue.addChapter("第一章 标题")
catalogue.addChapter("第二章 标题")
return catalogue
def getPageCount(self):
return self.__pageCount
def getPage(self, pageNum):
return Page(pageNum)
class Outline:
"""第三方PDF解析库的目录类"""
def __init__(self):
self.__outlines = []
def addOutline(self, title):
self.__outlines.append(title)
def getOutlines(self):
return self.__outlines
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
self.__title = ""
def open(self, filePath):
print("第三方库解析PDF文件:" + filePath)
self.__title = os.path.splitext(filePath)[0]
self.__pageSize = 1000
return True
def getTitle(self):
return self.__title
def getOutline(self):
outline = Outline()
outline.addOutline("第一章 PDF电子书标题")
outline.addOutline("第二章 PDF电子书标题")
return outline
def pageSize(self):
return self.__pageSize
def page(self, index):
return PdfPage(index)
class PdfAdapterBook(IBook):
"""对第三方的PDF解析库重新进行包装"""
def __init__(self, thirdPdf):
self.__thirdPdf = thirdPdf
def parseFile(self, filePath):
# 模拟文档的解析
rtn = self.__thirdPdf.open(filePath)
if rtn:
print(filePath + "文件解析成功")
return rtn
def getCatalogue(self):
outline = self.__thirdPdf.getOutline()
print("将Outline结构的目录转换成Catalogue结构的目录")
catalogue = Catalogue(self.__thirdPdf.getTitle())
for title in outline.getOutlines():
catalogue.addChapter(title)
return catalogue
def getPageCount(self):
return self.__thirdPdf.pageSize()
def getPage(self, pageNum):
page = self.__thirdPdf.page(pageNum)
print("将PdfPage的面对象转换成Page的对象")
return Page(page.getPageNum())
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(ThirdPdf())
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):
print("往前翻一页:", end="")
return self.gotoPage(self.__curPageNum - 1)
def nextPage(self):
print("往后翻一页:", end="")
return self.gotoPage(self.__curPageNum + 1)
def gotoPage(self, pageNum):
if 1 < pageNum < self.__curBook.getPageCount() - 1:
self.__curPageNum = pageNum
print("显示第" + str(self.__curPageNum) + "页")
page = self.__curBook.getPage(self.__curPageNum)
page.getContent()
return page
# Test
# =======================================================================================================================
def testReader():
reader = Reader("阅读器")
if not reader.openFile("平凡的世界.txt"):
return
reader.showCatalogue()
reader.prePage()
reader.nextPage()
reader.nextPage()
reader.closeFile()
print()
if not reader.openFile("追风筝的人.epub"):
return
reader.showCatalogue()
reader.nextPage()
reader.nextPage()
reader.prePage()
reader.closeFile()
print()
if not reader.openFile("如何从生活中领悟设计模式.pdf"):
return
reader.showCatalogue()
reader.nextPage()
reader.nextPage()
reader.closeFile()
testReader()
应用场景
- 系统需要使用现有的类,而这些类的接口不符合现有系统的要求。
- 对已有的系统拓展新功能,尤其适用于在设计良好的系统框架下增加接入第三方的接口或第三方的 SDK。