适配器模式
适配器模式是一种接口适配技术,可以通过某个类来使用另一个接口与之不相容的类,运用此模式时,两个类中的接口都无需改动。例如,我们想把某个类从原先的应用场景中拿出来放在另一个环境下运行,而这个类又不能修改,那可以考虑适配器模式。
书上的例子:
假设有一个Page类用于渲染页面, 需要知道标题、正文段落以及“渲染器类的实例”。该例子将举出两个渲染器,其中TextRenderer具有相关接口,而HtmlWriter不具有相关接口,需要编写HtmlRenderer适配器类来适配接口。
顶层调用
def main():
paragraph1 = MESSAGE.format("plain-text", "TextRenderer")
paragraph2 = """This is another short paragraph just so that we can
see two paragraphs in action."""
title = "Plain Text"
textPage = Page(title, TextRenderer(22))
textPage.add_paragraph(paragraph1)
textPage.add_paragraph(paragraph2)
textPage.render()
print()
paragraph1 = MESSAGE.format("HTML", "HtmlRenderer")
title = "HTML"
file = sys.stdout
htmlPage = Page(title, HtmlRenderer(HtmlWriter(file)))
htmlPage.add_paragraph(paragraph1)
htmlPage.add_paragraph(paragraph2)
htmlPage.render()
try:
page = Page(title, HtmlWriter())
page.render()
print("ERROR! rendering with an invalid renderer")
except TypeError as err:
print(err)
Page类实现
class Page:
def __init__(self, title, renderer):
if not isinstance(renderer, Renderer)
raise TypeError("Expected object of type Renderer, got {}".format(type(renderer).__name__))
def add_paragraph(self, paragraph):
self.paragraphs.append(paragraph)
def render(self):
self.renderer.header(self.title)
for paragraph in self.paragraphs:
self.renderer.paragraph(paragraph)
self.renderer.footer()
说明
要保证__init()__函数收到的确实是一个Renderer实例,可以使用assert isinstance(renderer,Renderer)。此方法有两个缺陷,一方面抛出的是AssertionError,而不是我们希望的TypeError。另一方面如果执行程序的时候使用-o(optimize,优化),则会忽略assert语句,导致AttributeError。于是使用如上代码的验证方式。但是也存在一个问题,就是传入的实例必须继承自Renderer基类。在Java或者c++中确实是这样,但是在python中可以采用另一种做法,既能像抽象基类那样检查接口是否匹配,又能像动态类型那样灵活,换句话说,可以在无需继承特定基类的前提下,创建出符合某套接口的对象。
Renderer基类的实现
isinstance()会调用__subclasshook__(Class,Subclass)来决定函数的首个参数是不是第二个参数的子类,或者是Subclass列表中某个类的子类,这里通过重写__subclasshook__(Class,Subclass),来实现上述说明中的情况
class Renderer(mateclass=abc.ABCMeta):
@classmethod
def __subclasshook__(Class, Subclass):
#判断确实是不是在Renderer上调用的,如果不是就返回NotImplement,并沿着继承体系按照通常的规则继续判定下去,这里会导致子类无法继承
if Class is Renderer:
#collections.ChainMap()将所有传进去的映射表当做一张表来看待,__mro__返回Subclass的包括其本身以及其超类的元组。
attributes = collections.ChainMap(*(Superclass.__dict__ for Superclass in Subclass.__mro__))
methods = ("header","paragraph","footer")
if all(method in attributes for method in methons):
return True
return NotImplemented
符合接口要求的TextRenderer
class TextRenderer:
def __init__(self, width=80, file=sys.stdout):
self.width = width
self.file = file
self.previous = False
def header(self, title):
self.file.write("{0:^{2}}\n{1:^{2}}\n".format(title,
"=" * len(title), self.width))
def paragraph(self, text):
if self.previous:
self.file.write("\n")
self.file.write(textwrap.fill(text, self.width))
self.file.write("\n")
self.previous = True
def footer(self):
pass
不符合接口要求的HtmlWriter
class HtmlWriter:
def __init__(self, file=sys.stdout):
self.file = file
def header(self):
self.file.write("<!doctype html>\n<html>\n")
def title(self, title):
self.file.write("<head><title>{}</title></head>\n".format(
escape(title)))
def start_body(self):
self.file.write("<body>\n")
def body(self, text):
self.file.write("<p>{}</p>\n".format(escape(text)))
def end_body(self):
self.file.write("</body>\n")
def footer(self):
self.file.write("</html>\n")
创建HtmlRenderer,适配Renderer接口
class HtmlRenderer:
def __init__(self, htmlWriter):
self.htmlWriter = htmlWriter
def header(self, title):
self.htmlWriter.header()
self.htmlWriter.title(title)
self.htmlWriter.start_body()
def paragraph(self, text):
self.htmlWriter.body(text)
def footer(self):
self.htmlWriter.end_body()
self.htmlWriter.footer()