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 類的部分,那麼適配器模式歡迎您。




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