現在、GoFデザインパターンを復習中。
前回までは生成パターンと構造パターンを掘り下げ
今回からは振る舞いパターンの残り6つ(Observer, State, Strategy, Template Method, Visitor)
通信販売サイトの商品状態をユーザーに通知する例
from __future__ import annotations
from abc import ABC, abstractmethod
from random import randrange
from typing import List
# 通知設定クラス
class Subject(ABC):
# 設定ON
@abstractmethod
def attach(self, status, observer: Observer) -> None:
pass
# 設定OFF
@abstractmethod
def detach(self, status, observer: Observer) -> None:
pass
# ユーザー通知
@abstractmethod
def notify(self) -> None:
pass
# 具象通知設定クラス
class ConcreteSubject(Subject):
_state: int = None
_observers: List[Observer] = []
# 設定ON
def attach(self, status, observer: Observer) -> None:
print(f"{status}: 通知設定をON")
self._observers.append(observer)
# 設定OFF
def detach(self, observer: Observer) -> None:
self._observers.remove(observer)
# ユーザー通知
def notify(self) -> None:
print("オブザーバーへ通知")
for observer in self._observers:
observer.update(self)
# ビジネスロジックフラグ
def some_business_logic(self, status) -> None:
print("通販サイト稼働中…")
self._state = status
print(f"商品状態の変更: {self._state}")
self.notify()
# オブザーバー(監視者)クラス
class Observer(ABC):
@abstractmethod
def update(self, subject: Subject) -> None:
pass
# 新商品入荷クラス
class ConcreteObserverA(Observer):
def update(self, subject: Subject) -> None:
if subject._state == 0:
print("【●通知】: 新商品が入荷しました\n")
# 在庫数クラス
class ConcreteObserverB(Observer):
def update(self, subject: Subject) -> None:
if subject._state == 1:
print("【●通知】: 在庫が残り5点となりました\n")
# 完売商品再入荷クラス
class ConcreteObserverC(Observer):
def update(self, subject: Subject) -> None:
if subject._state == 2:
print("【●通知】: 在庫切れの商品が入荷しました\n")
if __name__ == "__main__":
print("------ ユーザー設定 -----")
subject = ConcreteSubject()
# 新商品通知をON
observer_a = ConcreteObserverA()
subject.attach("【新商品】", observer_a)
# 在庫数通知をON
observer_b = ConcreteObserverB()
subject.attach("【在庫数】", observer_b)
# 再入荷通知をON
observer_c = ConcreteObserverC()
subject.attach("【再入荷】", observer_c)
print("\n")
subject.some_business_logic(0) # 新商品入荷
subject.some_business_logic(1) # 在庫数
print("------ 【新商品】通知設定を解除 -----")
subject.detach(observer_a) # 【新商品】通知設定を解除
subject.some_business_logic(0) # 新商品入荷
print("\n")
subject.some_business_logic(2) # 再入荷
------ ユーザー設定 -----
【新商品】: 通知設定をON
【在庫数】: 通知設定をON
【再入荷】: 通知設定をON
通販サイト稼働中…
商品状態の変更: 0
オブザーバーへ通知
【●通知】: 新商品が入荷しました
通販サイト稼働中…
商品状態の変更: 1
オブザーバーへ通知
【●通知】: 在庫が残り5点となりました
------ 【新商品】通知設定を解除 -----
通販サイト稼働中…
商品状態の変更: 0
オブザーバーへ通知
通販サイト稼働中…
商品状態の変更: 2
オブザーバーへ通知
【●通知】: 在庫切れの商品が入荷しました
天気の要求パターンによって傘と洗濯物の状態を変化させる例
class State():
def umbrella(self):
pass
def laundry(self):
pass
# 晴れの日クラス
class Sunnyday(State):
def umbrella(self):
print("傘は持たない")
def laundry(self):
print("屋外に干す")
# 曇りの日クラス
class Cloudyday(State):
def umbrella(self):
print("折りたたみ傘を持参")
def laundry(self):
print("屋内で乾かす")
# 雨の日クラス
class Rainyday(State):
def umbrella(self):
print("通常の傘を持参")
def laundry(self):
print("屋内で乾かす")
class Context:
def __init__(self):
self.sunny = Sunnyday()
self.rainy = Rainyday()
self.cloudy = Cloudyday()
self.state = self.sunny
def change_state(self, weather):
if weather == "sunny":
self.state = self.sunny
elif weather == "rain":
self.state = self.rainy
elif weather == "cloud":
self.state = self.cloudy
else:
raise ValueError("change_stateメソッドは次のようになっている必要があります {}".format(["sunny", "rain", "cloud"]))
def umbrella(self):
self.state.umbrella()
def laundry(self):
self.state.laundry()
if __name__ == "__main__":
# インスタンス化
obj = Context()
print("---------------------------")
# 雨の日パターンを実行
obj.change_state("rain")
obj.umbrella()
obj.laundry()
print("---------------------------")
# 晴れの日パターンを実行
obj.change_state("sunny")
obj.umbrella()
obj.laundry()
print("---------------------------")
# 嵐の日パターンを実行
obj.change_state("storm")
obj.umbrella()
obj.laundry()
---------------------------
通常の傘を持参
屋内で乾かす
---------------------------
傘は持たない
屋外に干す
---------------------------
# 嵐の日(storm)はパターンに設定していないので
ValueError: change_stateメソッドは次のようになっている必要があります ['sunny', 'rain', 'cloud']
アルゴリズムを切り替えるサンプル
from abc import ABCMeta, abstractmethod
class Strategy(metaclass=ABCMeta):
@abstractmethod
def run(self):
pass
# アルゴリズム1パターンクラス
class StrategyAlgorithm1(Strategy):
def run(self):
print('アルゴリズム1での処理')
# アルゴリズム2パターンクラス
class StrategyAlgorithm2(Strategy):
def run(self):
print('アルゴリズム2での処理')
class Context:
def __init__(self, strategy):
self.strategy = strategy
def set_strategy(self, strategy):
self.strategy = strategy
def do_strategy(self):
self.strategy.run()
if __name__ == '__main__':
# 初期アルゴリズムを設定
c = Context(StrategyAlgorithm1())
c.do_strategy()
# アルゴリズムを切り替え
c.set_strategy(StrategyAlgorithm2())
c.do_strategy()
アルゴリズム1での処理
アルゴリズム2での処理
タイトルと本文をプレーンテキスト形式とHTML形式の2パターンのテンプレートで出力
from abc import ABCMeta, abstractmethod
class AbstractReport(metaclass=ABCMeta):
def __init__(self, title, text_list):
self.title = title
self.text_list = text_list
@abstractmethod
def pprint(self):
pass
@abstractmethod
def start(self):
pass
@abstractmethod
def end(self):
pass
def display(self):
self.start()
self.pprint()
self.end()
class PlaneTextReport(AbstractReport):
def pprint(self):
for text in self.text_list:
print(text)
def start(self):
title = "**** #"+self.title+" ****"
print(title)
def end(self):
pass
class HtmlReport(AbstractReport):
def pprint(self):
for text in self.text_list:
print('<p>'+text+'</p>')
def start(self):
title = "<html><head>"
title += "<title>"+self.title+"</title>"
title += "</head><body>"
print(title)
def end(self):
title = "</body></html>"
print(title)
if __name__ == '__main__':
title = "タイトル"
text = [
"1行目",
"2行目",
"3行目"
]
# プレーンテキスト形式
plane_report = PlaneTextReport(title, text)
plane_report.display()
print()
# HTML形式
html_report = HtmlReport(title, text)
html_report.display()
print()
**** #タイトル ****
1行目
2行目
3行目
<html><head><title>タイトル</title></head><body>
<p>1行目</p>
<p>2行目</p>
<p>3行目</p>
</body></html>
社員が”会社へ通勤”と”取引先へ訪問”、またはその逆の”会社が社員を受け入れ”と”取引先が客を受け入れ”をまとめるサンプル
from abc import ABC, abstractmethod
# 訪問先抽象クラス
class Acceptor(ABC):
@abstractmethod
def accept(self, visitor):
pass
# 会社クラス
class Company(Acceptor):
def __init__(self):
self.__name = "会社"
self.__action = "出勤しました"
def getName(self) -> str:
return self.__name
def getAction(self) -> str:
return self.__action
def accept(self, visitor) -> None:
visitor.visit(self)
# 取引先クラス
class Suppliers(Acceptor):
def __init__(self):
self.__name = "取引先"
self.__action = "営業に向かいました"
def getName(self) -> str:
return self.__name
def getAction(self) -> str:
return self.__action
def accept(self, visitor) -> None:
visitor.visit(self)
# 訪問者抽象クラス
class Visitor(ABC):
@abstractmethod
def visit(self, acceptor) -> None:
pass
# 訪問者Aクラス
class VisitorA(Visitor):
def __init__(self):
self.__name = "佐藤さん"
def visit(self, accept: Acceptor) -> None:
print(f"{self.__name}が{accept.getName()}に{accept.getAction()}")
# 訪問者Bクラス
class VisitorB(Visitor):
def __init__(self):
self.__name = "鈴木さん"
def visit(self, accept: Acceptor) -> None:
print(f"{self.__name}が{accept.getName()}に{accept.getAction()}")
if __name__ == "__main__":
Company().accept(VisitorA()) # "会社" に "訪問者A" が訪問
VisitorA().visit(Company()) # "訪問者A" が "会社" に訪問
Suppliers().accept(VisitorA()) # "取引先" に "訪問者A" が訪問
VisitorA().visit(Suppliers()) # "訪問者A" が "取引先" に訪問
print("-" * 50)
Company().accept(VisitorB()) # "会社" に "訪問者B" が訪問
VisitorB().visit(Company()) # "訪問者B" が "会社" に訪問
Suppliers().accept(VisitorB()) # "取引先" に "訪問者B" が訪問
VisitorB().visit(Suppliers()) # "訪問者B" が "取引先" に訪問
佐藤さんが会社に出勤しました
佐藤さんが会社に出勤しました
佐藤さんが取引先に営業に向かいました
佐藤さんが取引先に営業に向かいました
--------------------------------------------------
鈴木さんが会社に出勤しました
鈴木さんが会社に出勤しました
鈴木さんが取引先に営業に向かいました
鈴木さんが取引先に営業に向かいました
visit()
メソッドも、受け入れるaccept()
メソッドも意味合いは異なるが、振る舞いは同じCompany
クラスと、取引先となるSuppliers
クラスのacceptメソッドでVisitorクラスの引数を持ち、visitメソッドを呼び出しすVisitor
クラスを実装する事で、訪問者の具象クラス(佐藤&鈴木)側はaccept()
メソッド1つを実装するだけでvisit()
メソッドと同じ結果を呼び出す事が可能になるCompany
クラスとSuppliers
クラス側も振る舞いに影響されなくなり、データ構造のみの構成が可能になる