备忘录模式和观察者模式 15/30 | Python 主题月
写在前面
本文正在参加「Python主题月」,详情查看活动链接
这个月是 Python 活动月,我决定尝试用 Python 来刷这 30 天的每日一题和随机一题。然后如果周末有精力,我想捣鼓捣鼓这个python-patterns
设计模式对我来说更多的是学习而不是我的个人经验总结,所以我很可能理解偏,如果有大佬见到了请及时指出,我之所以选择在掘金来写一些个人的东西是因为这里的文章质量更高,我不希望后来者看到了这些文章被误导。
备忘录模式
from copy import copy, deepcopy
def memento(obj, deep=False):
state = deepcopy(obj.__dict__) if deep else copy(obj.__dict__)
def restore():
obj.__dict__.clear()
obj.__dict__.update(state)
return restore
class Transaction:
"""A transaction guard.
This is, in fact, just syntactic sugar around a memento closure.
"""
deep = False
states = []
def __init__(self, deep, *targets):
self.deep = deep
self.targets = targets
self.commit()
def commit(self):
self.states = [memento(target, self.deep) for target in self.targets]
def rollback(self):
for a_state in self.states:
a_state()
class Transactional:
"""Adds transactional semantics to methods. Methods decorated with
@Transactional will rollback to entry-state upon exceptions.
"""
def __init__(self, method):
self.method = method
def __get__(self, obj, T):
def transaction(*args, **kwargs):
state = memento(obj)
try:
return self.method(obj, *args, **kwargs)
except Exception as e:
state()
raise e
return transaction
class NumObj:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"<{self.__class__.__name__}: {self.value!r}>"
def increment(self):
self.value += 1
@Transactional
def do_stuff(self):
self.value = "1111" # <- invalid value
self.increment() # <- will fail and rollback
def main():
"""
>>> num_obj = NumObj(-1)
>>> print(num_obj)
<NumObj: -1>
>>> a_transaction = Transaction(True, num_obj)
>>> try:
... for i in range(3):
... num_obj.increment()
... print(num_obj)
... a_transaction.commit()
... print('-- committed')
... for i in range(3):
... num_obj.increment()
... print(num_obj)
... num_obj.value += 'x' # will fail
... print(num_obj)
... except Exception:
... a_transaction.rollback()
... print('-- rolled back')
<NumObj: 0>
<NumObj: 1>
<NumObj: 2>
-- committed
<NumObj: 3>
<NumObj: 4>
<NumObj: 5>
-- rolled back
>>> print(num_obj)
<NumObj: 2>
>>> print('-- now doing stuff ...')
-- now doing stuff ...
>>> try:
... num_obj.do_stuff()
... except Exception:
... print('-> doing stuff failed!')
... import sys
... import traceback
... traceback.print_exc(file=sys.stdout)
-> doing stuff failed!
Traceback (most recent call last):
...
TypeError: ...str...int...
>>> print(num_obj)
<NumObj: 2>
"""
if __name__ == "__main__":
import doctest
doctest.testmod(optionflags=doctest.ELLIPSIS)
观察者模式
from __future__ import annotations
from contextlib import suppress
from typing import List, Optional, Protocol
# define a generic observer type
class Observer(Protocol):
def update(self, subject: Subject) -> None:
pass
class Subject:
def __init__(self) -> None:
self._observers: List[Observer] = []
def attach(self, observer: Observer) -> None:
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer: Observer) -> None:
with suppress(ValueError):
self._observers.remove(observer)
def notify(self, modifier: Optional[Observer] = None) -> None:
for observer in self._observers:
if modifier != observer:
observer.update(self)
class Data(Subject):
def __init__(self, name: str = "") -> None:
super().__init__()
self.name = name
self._data = 0
@property
def data(self) -> int:
return self._data
@data.setter
def data(self, value: int) -> None:
self._data = value
self.notify()
class HexViewer:
def update(self, subject: Data) -> None:
print(f"HexViewer: Subject {subject.name} has data 0x{subject.data:x}")
class DecimalViewer:
def update(self, subject: Data) -> None:
print(f"DecimalViewer: Subject {subject.name} has data {subject.data}")
def main():
"""
>>> data1 = Data('Data 1')
>>> data2 = Data('Data 2')
>>> view1 = DecimalViewer()
>>> view2 = HexViewer()
>>> data1.attach(view1)
>>> data1.attach(view2)
>>> data2.attach(view2)
>>> data2.attach(view1)
>>> data1.data = 10
DecimalViewer: Subject Data 1 has data 10
HexViewer: Subject Data 1 has data 0xa
>>> data2.data = 15
HexViewer: Subject Data 2 has data 0xf
DecimalViewer: Subject Data 2 has data 15
>>> data1.data = 3
DecimalViewer: Subject Data 1 has data 3
HexViewer: Subject Data 1 has data 0x3
>>> data2.data = 5
HexViewer: Subject Data 2 has data 0x5
DecimalViewer: Subject Data 2 has data 5
# Detach HexViewer from data1 and data2
>>> data1.detach(view2)
>>> data2.detach(view2)
>>> data1.data = 10
DecimalViewer: Subject Data 1 has data 10
>>> data2.data = 15
DecimalViewer: Subject Data 2 has data 15
"""
if __name__ == "__main__":
import doctest
doctest.testmod()
小结
参考文献
- 无