デザインパターン(Design Pattern)#Adapter


設計を意識したコードが書けるようになる為に、デザインパターン修行しました。
他のDesign Patternもちょくちょく出していきます。

前置き

デザインパターンをどういう時に、何を、どう使うのかを理解することが一先ずの目標。
(Javaというか静的型付言語は初めてで、且つpython歴もそんなに長くないので、Pythonistaぽっくないところがあると思います。ご指摘ございましたらご教授ください。)

今回は、構造に関するパターンAdapter。

Adapterとは

Adapterパターンには以下の2種がある。
1. クラスによるAdapterパターン(継承を使ったもの)
2. インスタンスによるAdapterパターン(委譲を使ったもの)

2つのクラス間に変換構造を作り、別の用途に使えるように変換する。Apdaterは中間層の役割で、使う方と、使われる方は中身を知らなくても良い。要はクラスで返すのか、インスタンスで返すのかの違いかな。

概要

クラスによる継承Apapterパターンを使ったサンプルと委譲によるサンプルプログラムがあります。両者とも与えられた文字列を(文字列)や*文字列*のように表示します。

Bannerクラスには、文字列をカッコでくくって表示するshow_with_parenというメソッドと、文字列の前後に*印を付けて表示するshow_with_parenというメソッドが用意されています。Bannerクラスが既存のものだと仮定します。

Printインタフェースでは、文字列を弱く(カッコ付きで)表示するためのメソッドprint_weakと、文字列を強く表示するためのメソッドprint_strongが宣言されています。

アダプターの役割を担うのがPrinterBannerクラスです。このクラスは、提供されているBannerクラスを継承し、必要とされているPrinterインタフェースを実装する。これでPrinterBannerクラスはアダプターとしての機能を果たすことになります。

継承パターンのクラス図

Adapter 継承パターン

banner.py
class Banner():

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

    def show_with_paren(self):
        print('({0})').format(self.__string))

    def show_with_aster(self):
        print('({0})').format(self.__string))
printer.py
from abc import ABCMeta, abstractmethod

class Printer(metaclass=ABCMeta):

  @abstractmethod
  def print_weak(self):
      pass

  @abstractmethod      
  def print_strong(self):
      pass    
printer_banner.py
from banner import Banner


class PrinterBanner(Banner)

    def __init__(self, string):
        super().__init__(string)

    def print_weak(self):
        self.show_with_paren()

    def print_strong(self):
        self.show_with_aster()

PrintBannerクラスがAdapterの役割をしています。

Bannerクラスを継承して、show_with_parenメソッドとshow_with_asterメソッドを継承します。

更に、要求されているPrinterインタフェースをしてprint_weakメソッドとprint_strongメソッドを実装してます。

main.py
from printer_banner import PrinterBanner

if __name__ == '__main__':
    pb = PrinterBanner('Bye')
    pb.print_weak() 
    pb.print_strong()

Printerインタフェースを使ってprint_weak()とprint_strong()で出力している。mainの方では、PrinterBannerクラスがどのような実装になってるか知らないので、mainクラスを変更せずともPrinterBannerクラスの実装を変えることができる。

Adapter 委譲パターン

委譲によるサンプルプログラムはMainとBannerは継承パターンと同じです。異なるのはPrintBannerクラスだけ。つまり、Bannerクラスを利用して、Printerクラスと同じメソッドを持つクラスを実現しようという。

PrinterBannerクラスは、bannerフィールドでBannerクラスのインスタンスを保持します。このインスタンスはPrinterBannerクラスのコンストラクタで生成します。そして、print_weak及び、print_strongメソッドでは、そのbannerフィールドを介してshow_with_paren, show_with_asterメソッドを呼び出します。

継承を使った方だと、自分のスーパークラスから継承したshow_with_paren, show_with_asterメソッドを呼び出しますが、委譲のパターンではフィールド経由で呼び出しています。

PrinterBannerクラスのprint_weakメソッドが呼ばれた時、自分で処理するのではなく、別のインスタンス(Bannerのインスタンス)のshow_with_parenメソッドにお任せしている。これが委譲になります。

委譲パターンのクラス図

printer_banner.py
from printer import Printer
from banner import Banner


class PrinterBanner(Printer):

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

     def print_weak(self):
         self.banner.show_with_paren()

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

今度はPrinterクラスを継承して、まずBannerクラスのインスタンスを呼んでいる。

それで呼んだインスタンスに対してshow_with_parenメソッドとshow_with_asterメソッドを使ってprint_weakメソッドとprint_strongメソッドを実装している。

文言だけ変えてるけど、出力結果は一緒。

inheritance/main.py
(Bye)
*Bye*
delegation/main.py
(Hello)
*Hello*

まとめ

Adapterパターンは、既存のクラスに一皮かぶせて必要とするクラスを作る。つまり、インタフェースとなる部分が間に入って、異なる二者間のズレを埋め合わせる。

バグが出たとしても、既存のクラスにはバグがないことがわかっているのでAdapter役のクラスを重点的に調べればよいことになり、プログラムのチェックがとても楽になるのと、既存のクラスの仕様だけがわかれば、新しいクラスを作ることができる。

参考