Python デザインパターン サンプルコード Adapter
結城 浩「Java言語で学ぶデザインパターン入門」をPython化
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」
◆◆Adapter パターンの使われる場面◆◆
Pythonの「print」が「sys.stdout.write」のラッパーであることはあまりにも有名な話である。そういえば,丸カッコの中身の書式が同じだったような。「print」は改行や半角スペースを出力するので「sys.stdout.write」より少し余分なことをしていることになる。
このようにラッパーは,ラッピング対象の名前を変え,実行の内容は変えないかまたは少し変えたものである。
似ているが違うもの,例えば,平社員と管理職の給与計算,エコノミークラスとファーストクラスのサービスなどオプションの違いを見かけ上同じしてしまう。これは充電器のACアダプタのようにつながらないものをつなげてしまう接続装置なのである。つまり,一致してないインターフェイスを一致させてしまうのがアダプタである。
他から与えられたクラスが修正を禁止されている場面がよくあるが(それが普通である),元をそのままにして高品質化,高機能化できるのがAdapter パターンである。
ただ,似ているパターンも多いので,次項の関連するパターンを参照してほしい。
(2023-11-18)Python3.11で動作確認済み
◆◆Adapter パターンとは◆◆
GoFによれば,Adapter パターンの目的は,
「あるクラスのインタフェースを,クライアントが求める他のインタフェースへ変換する。Adapter パターンは,インタフェースに互換性のないクラス同士を組み合わせることができるようにする。」
GoFによれば,Adapter パターンの別名は,Wrapper パターンだそうだ。
GoFによれば,Adapter パターンは,次のような状況で使うことができる。
・既存のクラスを利用したいが,そのインタフェースが必要なインタフェースと一致していない場合。
・まったく無関係で予想もつかないようなクラス(必ずしも互換性のあるインタフェースを持つとは限らない)とも協調していける,再利用可能なクラスを作成したい場合。
・(オブジェクトに適用する Adapter パターンのみ)既存のサブクラスを複数利用したいが,それらすべてのサブクラスをさらにサブクラス化することで,そのインタフェースを適合させることが現実的でない場合。オブジェクトに適用する Adapter パターンでは,その親クラスのインタフェースを適合させればよい。
GoFによれば,関連するパターンは次のようなものである。
Bridge パターン: このパターンは,オブジェクトを基にした adapter によく似た構造をしているが,目的が異なっている。Bridge パターンの目的は,インタフェースと実装を分離して,それらを容易にかつ独立して変更できるようにすることである。Adapter パターンの目的は,“既存の”オブジェクトのインタフェースを変換することである。
Decorator パターン: このパターンでは,インタフェースを変更することなく,オブジェクトに対して機能の追加を行う。アプリケーションにとっては,adapter よりも decorator の方が透過性が高い。その結果 Decorator パターンは,純粋な adapter では不可能な,再帰的なオブジェクト構造をサポートする。
Proxy パターン: このパターンでは,他のオブジェクトに対する代表あるいは代理を定義するが,その際にインタフェースを変更することはない。
◆◆Adapter パターンのサンプルのクラス構成◆◆
ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。
Print Banner
(printWeak, printStrong) (showWithParen, showWithAster)
↑
PrintBanner
(printWeak, printStrong)
サンプルは,旧いクラスのメソッドを新しいクラスのメソッドでラッピングします。
class Print は,テンプレートであり,抽象クラスです。
class PrintBanner は,ラッパーを扱う具象クラスです。
class Banner は,旧いクラスを想定していて,ラッピング対象です。
◆◆Adapter パターンの実装(「Adapter1.py」はクラスの継承を利用したもの)◆◆
Python では言語仕様として @ マークを付けるデコレータでラッパーをつくることができます。それとは違い,ここではクラスの継承またはオブジェクトの委譲を利用したラッパーをデザインパターンとして紹介されています。
バナーは新聞の大きな見出しのことであり,バナー広告とはそれと同じように見せる広告である。このサンプルではごく簡単に文字列を丸カッコまたはアスタリスクで囲う加工をします。
「class Banner」が与えられた改造できないクラスと思っていただきたい。「class Banner」をラッピングするのが「class PrintBanner」です。1つ目のサンプルではこの2つのクラスを親子とするのです。「class Banner」がスーパークラスであり,「class PrintBanner」がサブクラスです。
ソースコードをみれば判るが,サブクラスのコンストラクタでスーパークラスのコンストラクタを呼んでいます。呼び方は2通りあるが,多重継承なのでコメントアウトしてある呼び方は親が曖昧になるので使わない方がよいでしょう。
このようにすると親のメソッドを子のインスタンスで呼ぶことができるようになるのです。サンプルでは,28行目と30行目の self が自分つまり子の「class PrintBanner」のインスタンスです。
「class PrintBanner」のインスタンスを使ってメソッドを呼ぶと本来「class PrintBanner」のメソッドを探します。このクラスの中に探しているメソッドがないと親の「class Banner」を探しに行くのです。ここがオブジェクト指向の重要なポイントです。
ラッパーの役目を果たすために「class PrintBanner」では自分が呼ばれたときに自分のインスタンスを使って自分にはないメソッドつまりラッピング対象のメソッドを呼んでいます。クライアント(ここではメインルーチン)から見れば区別する必要がないということになります。
本当にこんな動作ができるのか,36行目と37行目でサブクラスのインスタンスでスーパークラスのメソッドを呼んで検証しています。
◆◆Adapter パターンの実装(「Adapter2.py」はオブジェクトの委譲を利用したもの)◆◆
委譲は権限委譲のように委任や委託の意味ですが,譲(ゆず)るが強調されたものです。
「Adapter1.py」との違いは「class PrintBanner」だけです。「class Banner」との親子関係はありません。
「class PrintBanner」の中で「class Banner」をインスタンス化して,ラッパーのメソッドが呼ばれたらそのインスタンスを使って「class Banner」のメソッドを呼んでいるだけです。テクニックと言うほどのものではありませんが,これがオブジェクト指向のポイントです。
丸ごとラッピングするだけの例なのでメリットが実感できないかもしれないが,方法論としてはこれがすべてです。
◆◆ソースコード◆◆
このWebの左上隅からダウンロードできます。
ソースファイルは2つです。
・Adapter1.py;Adapter パターンのPythonサンプル(クラスの継承を利用したもの)
・Adapter2.py;Adapter パターンのPythonサンプル(オブジェクトの委譲を利用したもの)
【Adapter1.py】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from abc import ABCMeta, abstractmethod
class Banner(object):
def __init__(self, string):
self.string = string
def showWithParen(self):
sys.stdout.write("({0})\n".format(self.string))
def showWithAster(self):
sys.stdout.write("*{0}*\n".format(self.string))
class Print(metaclass = ABCMeta):
@abstractmethod
def printWeak(self):
pass
@abstractmethod
def printStrong(self):
pass
class PrintBanner(Banner, Print):
def __init__(self, string):
Banner.__init__(self, string)
#super(PrintBanner, self).__init__(string)
def printWeak(self):
self.showWithParen()
def printStrong(self):
self.showWithAster()
def main():
p = PrintBanner("Hello")
p.printWeak()
p.printStrong()
#p.showWithParen() #debug
#p.showWithAster() #debug
if __name__=="__main__":
main()
"""標準出力
(Hello)
*Hello*
"""
【Adapter2.py】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from abc import ABCMeta, abstractmethod
class Banner(object):
def __init__(self, string):
self.string = string
def showWithParen(self):
sys.stdout.write("({0})\n".format(self.string))
def showWithAster(self):
sys.stdout.write("*{0}*\n".format(self.string))
class Print(metaclass = ABCMeta):
@abstractmethod
def printWeak(self):
pass
@abstractmethod
def printStrong(self):
pass
class PrintBanner(Print):
def __init__(self, string):
self.banner = Banner(string)
def printWeak(self):
self.banner.showWithParen()
def printStrong(self):
self.banner.showWithAster()
def main():
p = PrintBanner("Hello")
p.printWeak()
p.printStrong()
if __name__=="__main__":
main()
"""標準出力
(Hello)
*Hello*
"""
以上