🎨

Pythonでデザインパターンを学ぼう (Adapter)

2023/05/05に公開

Pythonを用いてのGoFの定義した23個のデザインパターンの一つであるAdapterパターンの実装方法について解説します。

Adapterパターンは、「構造に関するデザインパターン」に分類されます。

Adapterパターンとは

システムを利用するクライアントは、簡単なインターフェースを通じてサブシステムと連携できるパターンです。
このパターンを使用することで、既存のクラスに対して変更を加えることなく、インターフェースを変更することができます。

利点

  • 既存のクラスを変更せずに新しいインターフェースと連携させることができます。これにより、コードの再利用が容易になり、保守性が向上します。

  • 柔軟性が向上し、既存のコードを継承や合成によって拡張できます。これにより、新しい機能を追加する際に、コードの変更が最小限になります。

欠点

  • Adapterパターンを過剰に使用すると、コードが複雑になり理解しにくくなる可能性があります。

Adapterパターンのクラス図

  • Target - 期待されるインターフェースを定義するクラス
  • Adaptee - 既存のインターフェースを持つクラス
  • Adapter - AdapteeのインターフェースをTargetのインターフェースに適合させるクラス

Adapterパターンは継承による実装委譲による実装の2パターンがあります。

継承のクラス図

委譲のクラス図

簡単なAdapterパターンのサンプル

main.pyを実行して、文字を()で閉じて表示するパターンと文字を*で閉じて表示するパターンの2つがあります。

文字を()で閉じて表示する show_with_parenメソッド

$ python main.py Hello
(Hello)

文字を*で閉じて表示する show_witH_weakメソッド

$ python main.py Hello
*Hello*

継承による実装

以下は、PythonでのAdapterパターンを継承による実装の簡単なサンプルコードです。

from abc import ABCMeta, abstractmethod


# Adaptee
class Banner:
    __text: str

    def __init__(self, text: str) -> None:
        self.__text = text

    def show_with_paren(self) -> None:
        print('({})'.format(self.__text))

    def show_with_aster(self) -> None:
        print('*{}*'.format(self.__text))


# Target
class Print(metaclass=ABCMeta):
    @abstractmethod
    def print_weak(self) -> None:
        raise NotImplementedError()

    @abstractmethod
    def print_strong(self) -> None:
        raise NotImplementedError()


# Adapter
class PrintBanner(Print, Banner):
    def __init__(self, text: str) -> None:
        super().__init__(text)

    def print_weak(self) -> None:
        self.show_with_paren()

    def print_strong(self) -> None:
        self.show_with_aster()


def main():
    p: PrintBanner = PrintBanner("Hello")
    p.print_weak()
    p.print_strong()

if __name__ == '__main__':
    main()

Adapteeの実装

既存のインターフェースを持つクラスです。
つまりは、既に既存の実装済みのクラスです。

from abc import ABCMeta, abstractmethod


# Adaptee
class Banner:
    __text: str

    def __init__(self, text: str) -> None:
        self.__text = text

    def show_with_paren(self) -> None:
        print('({})'.format(self.__text))

    def show_with_aster(self) -> None:
        print('*{}*'.format(self.__text))

Targetの実装

期待されるインターフェースを定義するクラスです。Pythonではインタフェースは無いため、抽象クラスを使用します。

from abc import ABCMeta, abstractmethod


# Target
class Print(metaclass=ABCMeta):
    @abstractmethod
    def print_weak(self) -> None:
        raise NotImplementedError()

    @abstractmethod
    def print_strong(self) -> None:
        raise NotImplementedError()

Adapterの実装

AdapteeのインターフェースをTargetのインターフェースに適合させるクラスです。
つまりは既に実装してしまったAdapteeのクラスを、Adapterである以下のPrintBannerクラスが代わりに実行しています。

# Adapter
class PrintBanner(Print, Banner):
    def __init__(self, text: str) -> None:
        super().__init__(text)

    def print_weak(self) -> None:
        self.show_with_paren()

    def print_strong(self) -> None:
        self.show_with_aster()


def main():
    p: PrintBanner = PrintBanner("Hello")
    p.print_weak()
    p.print_strong()

委譲による実装

以下は、PythonでのAdapterパターンを委譲による実装の簡単なサンプルコードです。
委譲による実装でもAdapteeであるBannerTargetであるPrintは全く同じです。

委譲による実装で違くなるのは、Adapterです。

from abc import ABCMeta, abstractmethod


# Adaptee
class Banner:
    __text: str

    def __init__(self, text: str) -> None:
        self.__text = text

    def show_with_paren(self) -> None:
        print('({})'.format(self.__text))

    def show_with_aster(self) -> None:
        print('*{}*'.format(self.__text))


# Target
class Print(metaclass=ABCMeta):
    @abstractmethod
    def print_weak(self) -> None:
        raise NotImplementedError()

    @abstractmethod
    def print_strong(self) -> None:
        raise NotImplementedError()


# Adapter
class PrintBanner(Print):
    __banner: Banner
    def __init__(self, text: str) -> None:
        self.__banner = Banner(string)

    def print_weak(self) -> None:
        self.__banner.show_with_paren()

    def print_strong(self) -> None:
        self.__banner.show_with_aster()


def main():
    p: PrintBanner = PrintBanner("Hello")
    p.print_weak()
    p.print_strong()

if __name__ == '__main__':
    main()

Adapterの実装

委譲による実装のAdapteeのインターフェースをTargetのインターフェースに適合させるクラスです。
継承による実装との違いは、TargetであるPrintを継承しているものの、AdapteeであるBannerクラスのインスタンスをコンストラクタ内で生成していることです。

# Adapter
class PrintBanner(Print):
    __banner: Banner
    def __init__(self, text: str) -> None:
        self.__banner = Banner(string)

    def print_weak(self) -> None:
        self.__banner.show_with_paren()

    def print_strong(self) -> None:
        self.__banner.show_with_aster()


def main():
    p: PrintBanner = PrintBanner("Hello")
    p.print_weak()
    p.print_strong()

Adapterパターンの使い道とは

Adapterパターンは、既存のクラスを修正せずに、新しいインターフェースに適合させたい場合に役立ちます。

他には、互換性のないインターフェースを持つクラスを統合したい場合クライアントがインターフェースに依存するような設計を行いたい場合などに役立ちます。

Adapterパターンを適切に使用することで、コードの再利用性が向上し、システム全体の柔軟性も高まります。

参考資料

https://www.techscore.com/tech/DesignPattern/Adapter/Adapter1

https://nishi2.info/pydp/GoF_dp/structure/06_Adapter/index.html

https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/pythonによるデザインパターン-adapter-42aeb502136d

Discussion