Adapterパターン

概要

既存のクラスを修正することなく、インターフェースを修正する」パターンです。

例題

月報をHTML形式で出力するプログラムを考えます。

  • HtmlWriter クラスがすでにあり、このクラスを使用します。

  • PlainTextReporterクラスもすでにあり、このクラスと同じメソッドで出力できるようにします。
    (インターフェースを同じにする)

Adapterパターンは「継承による実装」と「委譲による実装」の2パターンがあるため、2パターン両方で実装しています。

サンプルコード

import abc
import sys


def main():
    title = "Monthly Report"
    texts = ["good", "best"]

    pr = PlainTextReporter()
    pr.header(title)
    pr.main(texts)
    pr.footer()

    print("\n\n")

    hr = HtmlReporter()
    hr.header(title)
    hr.main(texts)
    hr.footer()


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

    # oldMethod
    def out_header(self):
        self.file.write("<!doctype html>\n<html>\n")

    # oldMethod
    def out_title(self, title):
        self.file.write("<head><title>{}</title></head>\n".format(title))

    # oldMethod
    def out_start_body(self):
        self.file.write("<body>\n")

    # oldMethod
    def out_body(self, texts):
        for text in texts:
            self.file.write("<p>{}</p>\n".format(text))

    # oldMethod
    def out_end_body(self):
        self.file.write("</body>\n")

    # oldMethod
    def out_footer(self):
        self.file.write("</html>\n")


# Target
class Reporter(metaclass=abc.ABCMeta):
    # requiredMethod()
    @abc.abstractmethod
    def header(self, title):
        pass

    # requiredMethod()
    @abc.abstractmethod
    def main(self, contents):
        pass

    # requiredMethod()
    @abc.abstractmethod
    def footer(self):
        pass


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

    # requiredMethod()
    def header(self, title):
        self.file.write("**{}**\n".format(title))

    # requiredMethod()
    def main(self, texts):
        for text in texts:
            self.file.write("{}\n".format(text))

    # requiredMethod()
    def footer(self):
        pass


# Adapter 継承Ver
class HtmlReporter(Reporter, HtmlWriter):
    def __init__(self, file=sys.stdout):
        self.file = file

    # requiredMethod()
    def header(self, title):
        self.out_header()
        self.out_title(title)
        self.out_start_body()

    # requiredMethod()
    def main(self, texts):
        self.out_body(texts)

    # requiredMethod()
    def footer(self):
        self.out_end_body()
        self.out_footer()


# Adapter 委譲Ver
class HtmlReporter(Reporter):
    def __init__(self, file=sys.stdout):
        self._htmlwriter = HtmlWriter(file)

    # requiredMethod()
    def header(self, title):
        self._htmlwriter.out_header()
        self._htmlwriter.out_title(title)
        self._htmlwriter.out_start_body()

    # requiredMethod()
    def main(self, texts):
        self._htmlwriter.out_body(texts)

    # requiredMethod()
    def footer(self):
        self._htmlwriter.out_end_body()
        self._htmlwriter.out_footer()


if __name__ == "__main__":
    main()

# 出力

**Monthly Report** good best <!doctype html> <html> <head><title>Monthly Report</title></head> <body> <p>good</p> <p>best</p> </body> </html>

特徴

継承バージョンで説明します。

class HtmlReporter(Reporter, HtmlWriter):
    # ...

    def header(self, title):
        self.out_header()
        self.out_title(title)
        self.out_start_body()

    def main(self, texts):
        # ...

まず1行目ですが、
HtmlWriterに対して「汎化(継承)」を、
Reporterに対して「実現」をしたいためこのような書き方になっています。
(クラス図を見るとわかりやすいと思います。)

Pythonでは両方共継承すればOKです。なお、Pythonでは多重継承が可能なため、これでもエラーはでません。

残りは見たとおりですが、

def header(self, title):
    self.out_header()
    self.out_title(title)
    self.out_start_body()

たとえば header メソッドに対して、元のout_header,out_title,out_start_bodyを使用することで代用しています。

Pythonらしく書くためのコツ

特になし

クラス図

# 一般的なクラス図(継承)

一般的なクラス図(Adapterパターン(継承))

# 一般的なクラス図(委譲)

一般的なクラス図(Adapterパターン(委譲))

# サンプルコードにおけるクラス図(継承)

サンプルコードにおけるクラス図(Adapterパターン(継承))

サンプルコードにおけるクラス図(委譲)

サンプルコードにおけるクラス図(Adapterパターン(委譲))

参考URL

実践Python3

Wikipedia(クラス図をお借りしました)