見出し画像

DP.09:対象オブジェクトを紐づけたオブジェクトを経由する+メソッド名を統一させる- Bridge -【Python】

【1】Bridgeパターン概要

Bridgeパターンでは、『オブジェクトをコールする時は、専用オブジェクト経由で行う。その際に、紐づけておく対象オブジェクトのメソッド名は統一できるように仕掛けを入れておく』という書き方。

画像1

専用オブジェクトを経由させるところは、Adaptorパターンとよく似ている。Bridgeパターンでは、紐づけるオブジェクトのメソッド名を事前に統一して作成するよう仕掛けを入れることで、attribute(属性)追加という手間がなくなる。

※Adaptorパターンでは紐づけるオブジェクトのソースコードは変更できない条件であったため、専用オブジェクトに後からattribute(属性:クラス変数やクラスメソッド相当)を追加する仕組みを利用していた。

【2】例:URLデータフェッチとテキストファイルフェッチオブジェクト

サンプルとして以下のようなものを作ってみる

URLデータフェッチオブジェクト:指定のURLからデータをhtmlを取ってくる
テキストファイルフェッチオブジェクト:指定のテキストファイルを開く

■読み込み用サンプルファイル(file.txt)
※ダミーテキスト:lorem ipsum(ロレム・イプサム)を適当に生成

Lorem ipsum dolor sit amet, his ne noster virtute eruditi. Sea no alii dictas aliquid, duo ut quot ubique. Aliquid denique ex est, vix ferri disputando ei. Audiam iuvaret sea ex. Vix stet tempor consequuntur ad, falli libris meliore ex his. Eam in graeci laboramus dissentiunt, in vix gloriatur percipitur.

Errem viris vis ne, partem albucius vivendum sea ex. Te vim affert dignissim maiestatis, in vis assum scripta labores. Nam in labitur denique intellegebat, dico erant utinam nam eu. Nonumes dolorem accusamus vel ex, ornatus senserit iudicabit nec no. Cu pri movet convenire definitionem, in mel minim fabulas. His id dico aliquid ocurreret, vel eius assum verterem cu.

■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()

#実行結果例
b'<!doctype html>\n<!--[if lt IE 7]> <html class="no-js ie6 lt-ie7 lt-ie8 lt-ie9"> <![endif]-->\n<!--[if IE 7]> <html class="no-js ie7 lt-ie8 lt-ie9"> <![endif]-->\n<!--[if IE

・・・(略)・・・

\n \n <![endif]-->\n\n <!--[if lte IE 8]>\n <script type="text/javascript" src="/static/js/plugins/getComputedStyle-min.d41d8cd98f00.js" charset="utf-8"></script>\n \n \n <![endif]-->\n\n \n\n \n \n\n</body>\n</html>\n'
*****************
Lorem ipsum dolor sit amet, his ne noster virtute eruditi. Sea no alii dictas aliquid, duo ut quot ubique. Aliquid denique ex est, vix ferri disputando ei. Audiam iuvaret sea ex. Vix stet tempor consequuntur ad, falli libris meliore ex his. Eam in graeci laboramus dissentiunt, in vix gloriatur percipitur.

Errem viris vis ne, partem albucius vivendum sea ex. Te vim affert dignissim maiestatis, in vis assum scripta labores. Nam in labitur denique intellegebat, dico erant utinam nam eu. Nonumes dolorem accusamus vel ex, ornatus senserit iudicabit nec no. Cu pri movet convenire definitionem, in mel minim fabulas. His id dico aliquid ocurreret, vel eius assum verterem cu.

▲動作としては問題ない。

↓ これに対してBridgeパターンを適用してみる。

着目点は2つ。

①統一したメソッド名になるように仕向ける仕掛けを入れる
②専用オブジェクト経由で対象のオブジェクトはコールする

画像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サーバ」に接続するような「フェッチ用オブジェクト」を追加する時に、対象オブジェクトのコールの仕方を大きく変える必要もなく、さらに実装するメソッド名や引数が決まっているので、コードを作りやすくなるかもしれない。

もっと応援したいなと思っていただけた場合、よろしければサポートをおねがいします。いただいたサポートは活動費に使わせていただきます。