Pythonでデザインパターンを学ぼう (Adapter)
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
であるBanner
、Target
である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パターンを適切に使用することで、コードの再利用性が向上
し、システム全体の柔軟性も高まります。
参考資料
Discussion