現在、GoFデザインパターンを復習中。
前回までは生成パターンと構造パターンを掘り下げ
今回からは振る舞いパターンの6つ(Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Mement)を掘り下げ
受信メールのアドレスごとの受信箱フィルターレベル設定の例
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any, Optional
# 処理ハンドラークラス
class Handler(ABC):
@abstractmethod
def set_next(self, handler: Handler) -> Handler:
pass
@abstractmethod
def handle(self, request) -> Optional[str]:
pass
# ハンドラーチェーンの管理クラス
class AbstractHandler(Handler):
_next_handler: Handler = None
def set_next(self, handler: Handler) -> Handler:
self._next_handler = handler
return handler
# 優先順位振り分け
@abstractmethod
def handle(self, request: Any) -> str:
if self._next_handler:
return self._next_handler.handle(request)
return None
# 仕事用アドレス管理クラス
class BusinessHandler(AbstractHandler):
def handle(self, request: Any) -> str:
if request == "仕事":
return f"└ ○ 上司からのメールを「{request}フォルダ」に移動しました"
else:
return super().handle(request)
# プライベート用アドレス管理クラス
class PrivateHandler(AbstractHandler):
def handle(self, request: Any) -> str:
if request == "プライベート":
return f"└ ○ 友人からのメールを「{request}フォルダ」に移動しました"
else:
return super().handle(request)
# 身元不明アドレス管理クラス
class UnknownHandler(AbstractHandler):
def handle(self, request: Any) -> str:
if request == "その他":
return f"└ ○ 登録済みのメールを「{request}フォルダ」に移動しました"
else:
return super().handle(request)
# クライアント側のメールボックス
def client_code(handler: Handler) -> None:
# アドレス帳から受信メールを選別
for folder in ["仕事", "プライベート", "その他", "スパム"]:
print(f"\n {folder}フォルダ")
result = handler.handle(folder)
if result:
print(f" {result}", end="")
# print(f" └ △ 「迷惑メール」フォルダに入れました", end="")
else:
print(f" └ ✕ スパムメールを「迷惑メール」フォルダに入れました", end="")
if __name__ == "__main__":
business = BusinessHandler() # 仕事用アドレス
private = PrivateHandler() # プライベート用アドレス
unknown = UnknownHandler() # その他アドレス
# メールフィルター優先度設定(仕事 > プライベート > 不明)
unknown.set_next(private).set_next(business)
print("【①メインフィルター】: 仕事とプライベート以外はスパム扱い")
client_code(private)
print("\n-------------------------------------------------------------\n")
print("【②サブフィルター】: 不明アドレスでも受信許可リストにあれば受信")
client_code(unknown)
Handler
クラスでhandle(ハンドラ)とset_next(優先順位)を管理AbstractHandler
クラスで各アドレス管理クラスとチェーンの関係性とを管理client_code
クラスで指定した優先度(インスタンス)条件に従って届いたメールのアドレスを受信箱or迷惑メールに振り分ける
【①メインフィルター】: 仕事とプライベート以外はスパム扱い
仕事フォルダ
└ ○ 上司からのメールを「仕事フォルダ」に移動しました
プライベートフォルダ
└ ○ 友人からのメールを「プライベートフォルダ」に移動しました
その他フォルダ
└ ✕ スパムメールを「迷惑メール」フォルダに入れました
スパムフォルダ
└ ✕ スパムメールを「迷惑メール」フォルダに入れました
-------------------------------------------------------------
【②サブフィルター】: 不明アドレスでも受信許可リストにあれば受信
仕事フォルダ
└ ○ 上司からのメールを「仕事フォルダ」に移動しました
プライベートフォルダ
└ ○ 友人からのメールを「プライベートフォルダ」に移動しました
その他フォルダ
└ ○ 登録済みのメールを「その他フォルダ」に移動しました
スパムフォルダ
└ ✕ スパムメールを「迷惑メール」フォルダに入れました
加算or減算の実行コマンドに実行内容と現在の値のパラメータ表示をカプセル化実装した場合
import abc
# 現在値表示クラス
class data:
_data = 0
def __init__(self, value=0) -> None:
data._data += value
@abc.abstractmethod
def add(self):
pass
@abc.abstractmethod
def subtract(self):
pass
def __str__(self) -> str:
return f"現在の値: {data._data}"
# 加算処理クラス
class Addition(data):
def __init__(self, value=0) -> None:
super().__init__(value)
def add(self, value):
data._data += value
def __str__(self) -> str:
return super().__str__()
# 減算処理クラス
class Subtraction(data):
def __init__(self, value=0) -> None:
super().__init__(value)
def subtract(self, value):
data._data -= value
def __str__(self) -> str:
return super().__str__()
# コマンド管理クラス
class Command():
@abc.abstractmethod
def execute(self):
pass
@abc.abstractmethod
def display(self):
pass
@abc.abstractmethod
def undo(self):
pass
# 加算コマンド
class AdditionCommand(Command):
def __init__(self, obj, value):
self.obj = obj
self.value = value
def execute(self):
self.obj.add(self.value)
def display(self):
print(f"-> 値を{self.value}増やしました")
print(f"-> {obj}")
print("----------------------------------------------------------------------------")
def undo(self, commandObj):
commandObj.set_command(SubtractionCommand(Subtraction(), self.value))
commandObj.invoke()
# 減算コマンド
class SubtractionCommand(Command):
def __init__(self, obj, value):
self.obj = obj
self.value = value
def execute(self):
self.obj.subtract(self.value)
def display(self):
print(f"-> 値を{self.value}減らしました")
print(f"-> {obj}")
print("----------------------------------------------------------------------------")
def undo(self, commandObj):
commandObj.set_command(AdditionCommand(Addition(), self.value))
commandObj.invoke()
# リクエスト管理
class ActionInvoker:
def __init__(self, command):
self.command = command
def set_command(self, command):
self.command = command
def invoke(self):
self.command.execute()
self.command.display()
def undo(self):
print("└操作をやり直し\n", end="")
self.command.undo(self)
if __name__ == '__main__':
obj = data(100)
command_addition = AdditionCommand(Addition(), 10) # 加算インスタンス
command_subtraction = SubtractionCommand(Subtraction(), 10) # 減算インスタンス
print(f"初期値:{obj}")
print("①値を10増やす")
action_invoker = ActionInvoker(command_addition)
action_invoker.invoke()
print("②値を10減らす")
action_invoker.set_command(command_subtraction)
action_invoker.invoke()
print("③②の操作を取消し")
action_invoker.undo()
print("④③の操作を取消し")
action_invoker.undo()
print("⑤④の操作を取消し")
action_invoker.undo()
command
クラスと、実際の指令役のAdditionCommand
とSubtractionCommand
にexecute(実行)、display(表示)、undo(やり直し)を用意ActionInvoker
クラスでset_command(指令)、invoke(起動&結果表示)、undo(やり直し)の具体的実行処理を管理
初期値:現在の値: 100
----------------------------------------------------------------------------
①値を10増やす
-> 値を10増やしました
-> 現在の値: 110
----------------------------------------------------------------------------
②値を10減らす
-> 値を10減らしました
-> 現在の値: 100
----------------------------------------------------------------------------
③②の操作を取消し
└操作をやり直し
-> 値を10増やしました
-> 現在の値: 110
----------------------------------------------------------------------------
④③の操作を取消し
└操作をやり直し
-> 値を10減らしました
-> 現在の値: 100
----------------------------------------------------------------------------
⑤④の操作を取消し
└操作をやり直し
-> 値を10増やしました
-> 現在の値: 110
# 抽象クラス
class AbstractExpression():
@staticmethod
def interpret():
pass
# 数値化クラス
class Number(AbstractExpression):
def __init__(self, value):
self.value = int(value)
def interpret(self):
return self.value
def __repr__(self):
return str(self.value)
# 結合クラス
class Add(AbstractExpression):
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self):
return self.left.interpret() + self.right.interpret()
def __repr__(self):
return f"({self.left} + {self.right})"
# 減算クラス
class Subtract(AbstractExpression):
"Non-Terminal Expression"
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self):
return self.left.interpret() - self.right.interpret()
def __repr__(self):
return f"({self.left} - {self.right})"
if __name__ == '__main__':
SENTENCE = "5 + 4 - 3 + 7 - 2"
print(f"文字列出力:{SENTENCE}")
# スペースごとに
splits = SENTENCE.split(" ")
print(f"①分割出力:{splits}")
# 単語から抽象構文木(Abstract Syntax Tree)を手動で作成
AST = []
AST.append(Add(Number(splits[0]), Number(splits[2]))) # 5 + 4
AST.append(Subtract(AST[0], Number(splits[4]))) # ^ - 3
AST.append(Add(AST[1], Number(splits[6]))) # ^ + 7
AST.append(Subtract(AST[2], Number(splits[8]))) # ^ - 2
print(f"②抽象構文木を探索:{AST}")
# 最後のAST行をルートノードとして使用
AST_ROOT = AST.pop()
#ルートから開始する完全なASTを再帰的に解釈
print(f"③数値として結合出力:{AST_ROOT.interpret()}")
# AST_ROOTの表現を印刷します
print(f"④抽象構文木を結合出力:{AST_ROOT}")
文字列出力:5 + 4 - 3 + 7 - 2
①分割出力:['5', '+', '4', '-', '3', '+', '7', '-', '2']
②抽象構文木を探索:[(5 + 4), ((5 + 4) - 3), (((5 + 4) - 3) + 7), ((((5 + 4) - 3) + 7) - 2)]
③数値として結合出力:11
④抽象構文木を結合出力:((((5 + 4) - 3) + 7) - 2)
②の部分で抽象構文木の構造を手動で書いたが、ここをCompositeパターンで再帰的に処理する構造にすると最適化されると思われる
音楽プレイヤーのプレイリストの変更例
from __future__ import annotations
from collections.abc import Iterable, Iterator
from typing import Any, List
# オーダークラス
class OrderIterator(Iterator):
_position: int = None
_reverse: bool = False
def __init__(self, collection: MusicCollection, reverse: bool = False) -> None:
self._collection = collection
self._reverse = reverse
self._position = -1 if reverse else 0
def __next__(self):
try:
value = self._collection[self._position]
self._position += -1 if self._reverse else 1
except IndexError:
raise StopIteration()
return value
# 楽曲オブジェクトコレクションクラス
class MusicCollection(Iterable):
def __init__(self, collection: List[Any] = []) -> None:
self._collection = collection
def __iter__(self) -> OrderIterator:
return OrderIterator(self._collection)
def get_reverse_iterator(self) -> OrderIterator:
return OrderIterator(self._collection, True)
def add_item(self, item: Any):
self._collection.append(item)
if __name__ == "__main__":
collection = MusicCollection()
collection.add_item("1曲目")
collection.add_item("2曲目")
collection.add_item("3曲目")
print("通常プレイリスト")
print("\n".join(collection))
print("")
print("逆順プレイリスト")
print("\n".join(collection.get_reverse_iterator()), end="")
通常プレイリスト
1曲目
2曲目
3曲目
逆順プレイリスト
3曲目
2曲目
1曲目
スマートスピーカーでたくさんのオブジェクトを仲裁クラスで連動させる例
from __future__ import annotations
from abc import ABC
# 仲裁クラス
class Mediator(ABC):
def notify(self, sender: object, event: str) -> None:
pass
# 仲裁具象クラス
class ConcreteMediator(Mediator):
def __init__(self, component1: Component1, component2: Component2, component3: Component3) -> None:
self._component1 = component1
self._component1.mediator = self
self._component2 = component2
self._component2.mediator = self
self._component3 = component3
self._component3.mediator = self
def notify(self, sender: object, event: str) -> None:
if event == "照明の電源OFF":
print("\n●照明が消えたので「消灯」イベントを連動")
self._component1.do_b()
elif event == "スマートロック":
print("\n●スマートロックが起動したので「お出かけ」イベントを連動")
self._component1.do_a()
self._component2.do_d()
self._component3.do_e()
elif event == "ペットカメラ録画":
print("\n●ライブ録画が起動したので「留守中」イベントを連動")
self._component3.do_f()
# コンポーネント管理クラス
class BaseComponent:
def __init__(self, mediator: Mediator = None) -> None:
self._mediator = mediator
@property
def mediator(self) -> Mediator:
return self._mediator
@mediator.setter
def mediator(self, mediator: Mediator) -> None:
self._mediator = mediator
# 消灯コンポーネントクラス
class Component1(BaseComponent):
def do_a(self) -> None:
print('└消灯コンポーネントが【照明の電源OFF】を開始')
self.mediator.notify(self, "照明の電源OFF")
def do_b(self) -> None:
print('└消灯コンポーネントが【テレビの電源OFF】を実行')
self.mediator.notify(self, "テレビの電源OFF")
# 留守中コンポーネントクラス
class Component2(BaseComponent):
def do_c(self) -> None:
print('└留守中コンポーネントが【スマートロック】を実行')
self.mediator.notify(self, "スマートロック")
def do_d(self) -> None:
print('└留守中コンポーネントが【ロボット掃除機】を実行')
self.mediator.notify(self, "ロボット掃除機")
# ペットコンポーネントクラス
class Component3(BaseComponent):
def do_e(self) -> None:
print('└ペットコンポーネントが【ペットカメラ録画】を実行')
self.mediator.notify(self, "ペットカメラ録画")
def do_f(self) -> None:
print('└ペットコンポーネントが【餌やり機】を実行')
self.mediator.notify(self, "餌やり機")
if __name__ == "__main__":
c1 = Component1() # 消灯コンポーネント
c2 = Component2() # 留守中コンポーネント
c3 = Component3() # ペットコンポーネント
mediator = ConcreteMediator(c1, c2, c3)
print('①音声「おやすみ」をリクエスト')
c1.do_a() # 照明OFFをトリガー
print("\n------------------------------------------------------------------\n\n", end="")
print('②音声「行ってきます」をリクエスト')
c2.do_c() # スマートロックをトリガー
①音声「おやすみ」をリクエスト
└消灯コンポーネントが【照明の電源OFF】を開始
●照明が消えたので「消灯」イベントを連動
└消灯コンポーネントが【テレビの電源OFF】を実行
------------------------------------------------------------------
②音声「行ってきます」をリクエスト
└留守中コンポーネントが【スマートロック】を実行
●スマートロックが起動したので「お出かけ」イベントを連動
└消灯コンポーネントが【照明の電源OFF】を開始
●照明が消えたので「消灯」イベントを連動
└消灯コンポーネントが【テレビの電源OFF】を実行
└留守中コンポーネントが【ロボット掃除機】を実行
└ペットコンポーネントが【ペットカメラ録画】を実行
●ライブ録画が起動したので「留守中」イベントを連動
└ペットコンポーネントが【餌やり機】を実行
テキストをランダム文字列に変更した履歴からバックアップする例
from __future__ import annotations
from abc import ABC, abstractmethod
from datetime import datetime
from random import sample
from string import ascii_letters, digits
# オリジネータークラス
class Originator():
_state = None
def __init__(self, state: str) -> None:
self._state = state
print(f"オリジネーター: 初期状態: {self._state}")
def do_something(self) -> None:
print("オリジネーター: 作業中")
self._state = self._generate_random_string(30)
print(f"オリジネーター: 作業状態を変更しました: {self._state}")
def _generate_random_string(self, length: int = 10) -> None:
return "".join(sample(ascii_letters, length))
def save(self) -> Memento:
return ConcreteMemento(self._state)
def restore(self, memento: Memento) -> None:
self._state = memento.get_state()
print(f"オリジネーター: 状態を変更しました: {self._state}")
# 発信者クラス
class Memento(ABC):
@abstractmethod
def get_name(self) -> str:
pass
@abstractmethod
def get_date(self) -> str:
pass
# 発信者具象クラス
class ConcreteMemento(Memento):
def __init__(self, state: str) -> None:
self._state = state
self._date = str(datetime.now())[:19]
def get_state(self) -> str:
return self._state
def get_name(self) -> str:
return f"{self._date} / ({self._state}...)"
def get_date(self) -> str:
return self._date
# 世話人クラス
class Caretaker():
def __init__(self, originator: Originator) -> None:
self._mementos = []
self._originator = originator
def backup(self) -> None:
print("世話人: 現在の状態をバックアップ...\n")
self._mementos.append(self._originator.save())
def undo(self) -> None:
if not len(self._mementos):
return
memento = self._mementos.pop()
print(f"世話人: 状態を復元: {memento.get_name()}")
try:
self._originator.restore(memento)
except Exception:
self.undo()
def show_history(self) -> None:
print("世話人: 現在の変更履歴:")
for memento in self._mementos:
print(f"└{memento.get_name()}")
if __name__ == "__main__":
originator = Originator("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
caretaker = Caretaker(originator)
print()
originator.do_something() # 1回目の編集
caretaker.backup() # 状態をバックアップ
originator.do_something() # 2回目の編集
caretaker.backup() # 状態をバックアップ
originator.do_something() # 3回目の編集
caretaker.backup() # 状態をバックアップ
print("\n----------変更履歴------------")
caretaker.show_history()
print("\n----------復元リクエスト------------")
caretaker.undo()
print("\n----------再度復元リクエスト----------")
caretaker.undo()
オリジネーター: 初期状態: ABCDEFGHIJKLMNOPQRSTUVWXYZ
オリジネーター: 作業中
オリジネーター: 作業状態を変更しました: cjXpNGUAVbWJykzfKBsaZQMIhSYFOv
世話人: 現在の状態をバックアップ...
オリジネーター: 作業中
オリジネーター: 作業状態を変更しました: cAwSBCuEfblIJMrNYvGeiOxVWnXqyj
世話人: 現在の状態をバックアップ...
オリジネーター: 作業中
オリジネーター: 作業状態を変更しました: OqsAfEJTXyWStGlzUdKMChRjZcnrYm
世話人: 現在の状態をバックアップ...
----------変更履歴------------
世話人: 現在の変更履歴:
└2022-01-01 15:06:53 / (cjXpNGUAVbWJykzfKBsaZQMIhSYFOv...)
└2022-01-01 15:06:53 / (cAwSBCuEfblIJMrNYvGeiOxVWnXqyj...)
└2022-01-01 15:06:53 / (OqsAfEJTXyWStGlzUdKMChRjZcnrYm...)
----------復元リクエスト------------
世話人: 状態を復元: 2022-01-01 15:06:53 / (OqsAfEJTXyWStGlzUdKMChRjZcnrYm...)
オリジネーター: 状態を変更しました: OqsAfEJTXyWStGlzUdKMChRjZcnrYm
----------再度復元リクエスト----------
世話人: 状態を復元: 2022-01-01 15:06:53 / (cAwSBCuEfblIJMrNYvGeiOxVWnXqyj...)
オリジネーター: 状態を変更しました: cAwSBCuEfblIJMrNYvGeiOxVWnXqyj