さるへい備忘録

さるへいのやったことを綴っているブログです。基本的にテクノロジーの話題です。

Pythonで学ぶデザインパターン入門 Adapterパターン

結城先生の Java言語で学ぶデザインパターン入門 のコードを Python に書き直していくシリーズ第2段です。

Adapterパターンとは

adapterパターンとは、その名の通り電源に対する電源アダプターのポジションです。
「すでに提供されているもの」がそのまま使えない場合、それに対して上からかぶせて出力を変換するようなものですね。

ソースコード

コードの概要

ここでは、 Print 抽象クラスとして必要な機能が定義されています。それを Banner クラスの文字を整形して出力するという機能に対して、PrintBanner クラスでかぶせて必要な機能を作成するといったコードです。 つまり、仕様が Print クラスで、電源が Banner クラスで、電源アダプターが PrintBanner クラスです。 Banner クラスでは、

  • 文字列を " ではさんで表示する
  • 文字列を * ではさんで表示する

の2種類の機能が用意されており、それぞれが Print クラスに対して弱く出力する、強調して出力するに該当する出力となります。

Adapterパターンには、

  • 継承をつかったパターン
  • 移譲を使ったパターン

の2種類があるのでその両方のコードを紹介します。移譲は、処理を別のところに任せるという意味になります。

ソースコード

Bannerクラス(もともと用意されたもの)

# coding: utf-8


class Banner(object):

    def __init__(self, string: str):
        self.__string = string

    def show_with_paren(self):
        print('"%s"' % self.__string)

    def show_with_aster(self):
        print('*%s*' % self.__string)

前述の通り、2種類の出力機能があります。

Printクラス (必要な機能の定義)

# coding: utf-8
from abc import ABCMeta


class Print(metaclass=ABCMeta):

    def print_week(self):
        pass

    def print_strong(self):
        pass

前述の通り、2種類の機能を必要としています。

javaだったら、継承の場合はinterface、移譲の場合は抽象クラスになります。
pythonはinterfaceがないので両方抽象クラスで表現しました。

PrintBanner クラス (adapter)

継承
# coding: utf-8
from adapter.inherit.banner import Banner
from adapter.inherit.print import Print


class PrintBanner(Banner, Print):

    def print_week(self):
        self.show_with_paren()

    def print_strong(self):
        self.show_with_aster()

継承の場合はただ継承しているだけですね。わかりやすいといえばわかりやすいです。
わかりやすい反面、制限が多いので継承はコードの自由度が低めなのでよく考えて使ったほうが良いでしょう。

移譲
# coding: utf-8
from adapter.delegation.banner import Banner
from adapter.delegation.print import Print


class PrintBanner(Print):

    def __init__(self, string: str):
        self.__banner = Banner(string)

    def print_week(self):
        self.__banner.show_with_paren()

    def print_strong(self):
        self.__banner.show_with_aster()

移譲は、コンストラクタでインスタンスを生成するなどしてそこに処理を移譲します。
継承する必要がないので、自由度は高いです。

実行ファイル

# coding: utf-8
from adapter.inherit.print_banner import PrintBanner

if __name__ == '__main__':
    p = PrintBanner('Hello')
    p.print_week()
    p.print_strong()

こちらは、継承と移譲でimportが違うのですが、些細な違いなので片方だけ記載します。

実際どう便利か

めちゃめちゃ便利です。
よく考えて使わないと複雑になりがちなのですが、既存の機能に修正をいれるなどをせずに追加実装ができたりするので、とても便利です。
オープン、クローズドの原則を叶えるといった話でもとても良いものなので積極的に使ってます。
処理の共存というものは、往々にして起こりがちですがそういったものへの解決策にもなります。

ただ、かけ離れた処理を無理やりadapterパターンで期待するものへ機能を書き換えるならば、諦めて新しく処理を実装しましょう。