前回からGofデザインパターンを復習中。
今回は構造パターン7種のうちのAdapter, Bridge, Composite, Decoratorの4つを掘り下げる
# 自社アプリで使う通常の出力クラス(XML形式)
class ExportXML:
def request(self) -> str:
return "通常のXML形式で出力しますた"
# サードパーティから利用する出力クラス(JSON形式)
class ExportJSON:
def specific_request(self) -> str:
return "たすまし力出で式形NOSJの常通"
# サードパーティ出力を通常クラス用に変換するアダプタークラス
class Adapter(ExportXML, ExportJSON):
def request(self) -> str:
return f"{self.specific_request()[::-1]}"
# クライアントからの出力インターフェーズ(デフォはXMLのみ受け取り)
def client_code(target: "ExportXML") -> None:
print(target.request(), end="\n\n")
if __name__ == "__main__":
# XML出力インスタンス & 出力リクエスト
xml = ExportXML()
client_code(xml)
# --> 「通常のXML形式で出力しますた」
# JSON出力インスタンス& 直接実行
json = ExportJSON()
print(f"{json.specific_request()}", end="\n\n")
# --> 「たすまし力出で式形NOSJの常通」
# XMLクラスのみに限定しているのでエラー
client_code(json)
# アダプター変換 & 出力リクエスト
adapter = Adapter()
client_code(adapter)
# --> 「通常のJSON形式で出力しますた」
class ExportXML:
def request(self) -> str:
return "通常のXML形式で出力しますた"
class ExportJSON:
def specific_request(self) -> str:
return "たすまし力出で式形NOSJの常通"
class ExportCSV:
def specific_request(self) -> str:
return "たすまし力出で式形VSCの常通"
class Adapter(ExportXML):
def __init__(self, export) -> None:
self.export = export
def request(self) -> str:
return f"{self.export.specific_request()[::-1]}"
def client_code(target: "ExportXML") -> None:
print(target.request(), end="\n\n")
if __name__ == "__main__":
# XML出力インスタンス&出力リクエスト
xml = ExportXML()
client_code(xml)
# --> 「通常のXML形式で出力しますた」
# JSON出力インスタンス& 直接実行
json = ExportJSON()
adapter = Adapter(json)
client_code(adapter)
# --> 「通常のJSON形式で出力しますた」
# CSV形式でも出力
csv = ExportCSV()
adapter = Adapter(csv)
client_code(adapter)
# --> 「通常のJSON形式で出力しますた」
委譲パターンの場合はAdapterを実行するクラスを指定した上で同様の変換を処理できるので、
形式種類の追加も容易になる
import random
class Display():
"""
表示を扱う機能側の上位クラス(Bridgeの役目)
環境側のインスタンスを受け取りそれを使って,呼んだクラスの固有の動作をする
"""
def __init__(self, impl) -> None:
self.__impl = impl
def open(self) -> None:
self.__impl.raw_open()
def print(self) -> None:
self.__impl.raw_print()
def close(self) -> None:
self.__impl.raw_close()
def display(self) -> None:
self.open()
self.print()
self.close()
class CountDisplay(Display):
"""
表示回数を指定した機能側のサブクラス
"""
def multi_display(self, times: int) -> None:
self.open()
for _ in range(times):
self.print()
self.close()
class RandomDisplay(CountDisplay):
"""
表示回数がランダムに指定される機能側のサブクラス
"""
def random_display(self, times: int) -> None:
self.multi_display(random.randint(0, times))
class DisplayImp(metaclass=ABCMeta):
"""実装側の表示管理クラス"""
@abstractmethod
def raw_open(self) -> None:
pass
@abstractmethod
def raw_print(self) -> None:
pass
@abstractmethod
def raw_close(self) -> None:
pass
class StringDisplay(DisplayImp):
"""
文字列を使用した表示を行うクラス
実際に文字を出力している環境側の具象クラス
"""
def __init__(self, string: str) -> None:
self.string = string
self.width = len(string)
def raw_open(self) -> None:
self.print_line()
def raw_print(self) -> None:
print(f'|{self.string}|')
def raw_close(self) -> None:
self.print_line()
def print_line(self) -> None:
print('+', end='')
for i in range(self.width):
print('-', end='')
print('+')
if __name__=='__main__':
# 文字列出力クラスを継承した表示クラスで標準出力
display = Display(StringDisplay('通常の表示'))
display.display()
# 文字列出力クラスを継承した表示回数クラスで5回出力
count_display = CountDisplay(StringDisplay('回数指定で表示'))
count_display.multi_display(5)
# 文字列出力クラスを継承したランダム表示クラスで1〜10の間でランダム出力「
random_count_display = RandomDisplay(StringDisplay('ランダム表示'))
random_count_display.random_display(10)
StringDisplay
だけを替えればよいCountDisplay
や RandomDisplay
だけを替えればよい
+-----+
|通常の表示|
+-----+
+-------+
|回数指定で表示|
|回数指定で表示|
|回数指定で表示|
|回数指定で表示|
|回数指定で表示|
+-------+
+------+
|ランダム表示|
|ランダム表示|
+------+
関係のないクラス同士をつなぐことが目的という意味ではAdapterと似ているが、Adapterは設計が終わった後で適用させ、Bridgeは抽出されたクラスと実装を独立に変更可能にするために設計の前段階で使われる
class DataObject():
"""
コンポーネント
コンポジットとリーフの間の共通インターフェース
"""
def read(self): pass
class DataNode(DataObject):
"""
リーフノード(子)
コンポジットではないデータを含む
"""
def __init__(self, data):
self._data = data
def read(self):
print(" ┗ ファイル: ", self._data)
class DataComposite(DataObject):
"""
コンポジットノード(親)
子ノードを追加・削除するためのインターフェース
"""
def __init__(self, data):
self._meta_data = data
self.sub_objects = []
def read(self):
print("ディレクトリ: ", self._meta_data)
for data_object in self.sub_objects:
data_object.read()
def add(self, data_object):
self.sub_objects.append(data_object)
def remove(self, data_object):
self.sub_objects.remove(data_object)
if __name__ == "__main__":
# 個別ファイル定義
book_A1 = DataNode("プログラミング言語 Ruby")
book_A2 = DataNode("Python クックブック 第2版")
book_B1 = DataNode("JavaScript 第7版")
# ディレクトリ定義
book_A = DataComposite("バックエンド")
book_B = DataComposite("フロントエンド")
# 構造ルートの定義
book_root = DataComposite("オライリー参考書")
# 子ノードに本を追加
book_A.add(book_A1)
book_A.add(book_A2)
book_B.add(book_B1)
# 親ノードに分類別で追加
book_root.add(tree_A)
book_root.add(tree_B)
# 現在の構造を一括出力
tree_root.read()
ディレクトリ: オライリー参考書
┗ ディレクトリ: バックエンド
┗ ファイル: プログラミング言語 Ruby
┗ ファイル: Python クックブック 第2版
┗ ディレクトリ: フロントエンド
┗ ファイル: JavaScript 第7版
寒ければセーターを着させて、雨が降っていればさらにレインコートを着させられるが、いつでも脱ぐことができるイメージ
# ベースコンポーネント
class Component():
def operation(self) -> str:
pass
# 裸クラス
class ConcreteComponent(Component):
def operation(self) -> str:
return "裸一貫"
# デコレータークラス
class Decorator(Component):
_component: Component = None
def __init__(self, component: Component) -> None:
self._component = component
@property
def component(self) -> str:
return self._component
def operation(self) -> str:
return self._component.operation()
# セータークラス
class sweaterDecorator(Decorator):
def operation(self) -> str:
return f"セーター【{self.component.operation()}】"
# コートクラス
class coatDecorator(Decorator):
def operation(self) -> str:
return f"コート【{self.component.operation()}】"
# 出力インターフェース
def client_code(component: Component) -> None:
print(f"{component.operation()}", end="\n\n")
if __name__ == "__main__":
simple = ConcreteComponent()
client_code(simple)
# セーター -> コートを着用
sweater = sweaterDecorator(simple) # セーター着用
coat = coatDecorator(sweater) # コート着用
client_code(coat)
# 逆順に着用
coat = coatDecorator(simple)
sweater = sweaterDecorator(coat)
client_code(sweater)
裸一貫
コート【セーター【裸一貫】】
セーター【コート【裸一貫】】