DP.09:対象オブジェクトを紐づけたオブジェクトを経由する+メソッド名を統一させる- Bridge -【Python】
【1】Bridgeパターン概要
Bridgeパターンでは、『オブジェクトをコールする時は、専用オブジェクト経由で行う。その際に、紐づけておく対象オブジェクトのメソッド名は統一できるように仕掛けを入れておく』という書き方。
専用オブジェクトを経由させるところは、Adaptorパターンとよく似ている。Bridgeパターンでは、紐づけるオブジェクトのメソッド名を事前に統一して作成するよう仕掛けを入れることで、attribute(属性)追加という手間がなくなる。
※Adaptorパターンでは紐づけるオブジェクトのソースコードは変更できない条件であったため、専用オブジェクトに後からattribute(属性:クラス変数やクラスメソッド相当)を追加する仕組みを利用していた。
【2】例:URLデータフェッチとテキストファイルフェッチオブジェクト
サンプルとして以下のようなものを作ってみる
■読み込み用サンプルファイル(file.txt)
※ダミーテキスト:lorem ipsum(ロレム・イプサム)を適当に生成
■Bridgeパターン未適用版
import urllib.parse
import urllib.request
# 指定のURLをフェッチしてデータを出力する
class URLFetcher():
def fetch(self, path):
req = urllib.request.Request(path)
with urllib.request.urlopen(req) as response:
if response.code == 200:
the_page = response.read()
print(the_page)
# 指定のテキストをフェッチしてデータを出力する
class LocalFileFetcher():
def load(self, path):
with open(path) as f:
print(f.read())
### 動作確認 ###
def main():
url_fetcher = URLFetcher()
url_fetcher.fetch('https://www.python.org/')
print("*****************")
localfile_fetcher = LocalFileFetcher()
localfile_fetcher.load("file.txt")
if __name__ == '__main__':
main()
▲動作としては問題ない。
↓ これに対してBridgeパターンを適用してみる。
着目点は2つ。
【3】abc(Abstract Base Class)でメソッド名を統一する
作成するメソッドの名前を統一させるには「abc(Abstract Base Class)」を使う。
import abc
import urllib.parse
import urllib.request
# abc(Abstract Base Class)で実装必須にさせるメソッドを仕込む
class ResourceContentFetcher(metaclass = abc.ABCMeta):
@abc.abstractmethod
def fetch(self,path):
pass
# 抽象クラスを継承させるURLFetcherクラス
class URLFetcher(ResourceContentFetcher):
def fetch(self, path):
req = urllib.request.Request(path)
with urllib.request.urlopen(req) as response:
if response.code == 200:
the_page = response.read()
print(the_page)
# 抽象クラスを継承させるLocalFileFetcheクラス
class LocalFileFetcher(ResourceContentFetcher):
def fetch(self, path):
with open(path) as f:
print(f.read())
▲抽象クラスをつかうことで、fetch()の実装を必須とさせる。これによりfetch()というメソッド名で統一できる。
【4】専用オブジェクト経由で対象オブジェクトをコールさせる(Bridge:橋渡しする)
Adaptorパターンのように対象オブジェクトを紐づけた「橋渡し用のオブジェクト」を用意する。
# 対象オブジェクトをコールすときに経由させるオブジェクト
class ResourceContent:
def __init__(self, imp):
self._imp = imp # 対象オブジェクトを紐づける
def show_content(self, path):
# adaptorと違い対象オブジェクトのコールしたいメソッド名は決定済み
self._imp.fetch(path)
▲紐づけるオブジェクトのメソッド名が統一されているので、Adaptorパターンのようにsetattrなどを使った属性追加の手間はない。
■利用イメージ
# URLFetcherオブジェクト
url_fetcher = URLFetcher()
# 生成したオブジェクトは専用オブジェクト側から操作する(統一した記述ができる)
obj1 = ResourceContent(url_fetcher)
obj1.show_content('https://www.python.org/')
# LocalFileFetcherオブジェクト
localfile_fetcher = LocalFileFetcher()
# 生成したオブジェクトは専用オブジェクト側から操作する(統一した記述ができる)
obj2 = ResourceContent(localfile_fetcher)
obj2.show_content('file.txt')
【5】全体コード
import abc
import urllib.parse
import urllib.request
# abc(Abstract Base Class)で実装必須にさせるメソッドを仕込む
class ResourceContentFetcher(metaclass = abc.ABCMeta):
@abc.abstractmethod
def fetch(self,path):
pass
# 抽象クラスを継承させるクラスオブジェクトその1
class URLFetcher(ResourceContentFetcher):
def fetch(self, path):
req = urllib.request.Request(path)
with urllib.request.urlopen(req) as response:
if response.code == 200:
the_page = response.read()
print(the_page)
# 抽象クラスを継承させるクラスオブジェクトその2
class LocalFileFetcher(ResourceContentFetcher):
def fetch(self, path):
with open(path) as f:
print(f.read())
# 対象オブジェクトをコールすときに経由させるオブジェクト
class ResourceContent:
def __init__(self, imp):
self._imp = imp # 対象オブジェクトを紐づける
def show_content(self, path):
# adaptorと違い対象オブジェクトのコールしたいメソッド名は決定済み
self._imp.fetch(path)
def main():
url_fetcher = URLFetcher()
obj1 = ResourceContent(url_fetcher)
obj1.show_content('https://www.python.org/')
print("*****************")
localfile_fetcher = LocalFileFetcher()
obj2 = ResourceContent(localfile_fetcher)
obj2.show_content('file.txt')
if __name__ == '__main__':
main()
※実行結果に変わりはないので省略。
Bridgeパターンを使うと、作成する複数のオブジェクト間で実装を共有しやすい。(メソッド名や引数、返却値の統一化など)
例えば、今回の例に「FTPサーバ」や「DBサーバ」に接続するような「フェッチ用オブジェクト」を追加する時に、対象オブジェクトのコールの仕方を大きく変える必要もなく、さらに実装するメソッド名や引数が決まっているので、コードを作りやすくなるかもしれない。