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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huxiaotong_exp/article/details/54096772

适配器模式

适配器模式是一种接口适配技术,可以通过某个类来使用另一个接口与之不相容的类,运用此模式时,两个类中的接口都无需改动。例如,我们想把某个类从原先的应用场景中拿出来放在另一个环境下运行,而这个类又不能修改,那可以考虑适配器模式。


书上的例子:

假设有一个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()

猜你喜欢

转载自blog.csdn.net/huxiaotong_exp/article/details/54096772