Python的结构型设计模式之适配器模式

在学习完适配器模式之后,让我用一句话来总结之:就是把前一个类拿来用,用到你所希望它做的事。“适配器模式”是一种接口适配技术,可通过某个类来使用另一个接口与之不兼容的类,运用此模式时,两个类的接口都无须改动。今天看的例子是关于一个页面生成,以及对标题和段落进行渲染的 Page 类。首先是一个 Page 类。

class Page:
    def __init__(self, title, renderer):
        if not isinstance(renderer, Renderer):
            raise TypeError("Excepted object of type Render, got {}".format(type(renderer).__name__))

        self.title = title
        self.renderer = renderer
        self.paragraphs = []

    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()
除了标题和段落外,它需要知道一个渲染类 Renderer 的实例。
这里用到了 isintance() 来判断前者的类型
而 Renderer 类用来定义三个方法的接口

class Renderer(metaclass=abc.ABCMeta):
    @classmethod
    def __subclasshook__(Class, Subclass):
        if Class is Renderer:
            attribute = collections.ChainMap(*(Superclass.__dict__ for Superclass in Subclass.__mro__))
            methods = {"header", "paragraph", "footer"}
            if all(method in attribute for method in methods):
                return True
        return NotImplemented
你现在可能看不太懂这段代码,但是你需要明白的是这是定义 header、paragraph、footer 三个属性的接口就好。
因为在 Page 里面就是用 isinstance 来判断传入的类是否具有这三个行为。
首先看正常的渲染类:

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
三个方法都实现了,没毛病吧
然后看下一个:

class HtmlWriter:
    def __init__(self, file=sys.stdout):
        self.file = file

    def header(self):
        self.file.write("<document html>\n<html>\n")

    def title(self, title):
        self.file.write("<head><title>{}</title></head>".format(title))

    def start_body(self):
        self.file.write("<body>\n>")

    def body(self, text):
        self.file.write("<p>{}</p>".format(text))

    def end_body(self):
        self.file.write("</body>")

    def footer(self):
        self.file.write("</html>\n")
这个一看,有 header 和 footer ,你说那暂时不用渲染 paragraph 了,只渲染 title 也可以吧?
答案是不行的。
因为其行为和页面渲染器接口所定义的不同。所以不能直接用这个。怎么改呢?
到了这次的重点,创建适配器:把它当作参数,然后对其进行聚合,聚合成我们需要的三个行为:

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()
看见了吧,把 HtmlWrite 当作参数传进来之后,就可以进行聚合,把你认为能组合到一块的放在一起即可。
这就是适配器。
怎么样,看到这里觉得适配器模式还很简单吧。如果你想用 A 类,又想在别的地方用 A 类的部分,那么适配器模式欢迎您。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章