Example
- Newspaper Subscription
- Channel Subscription
- Any kind of push notification service
- Mostly used in GUIs
Advantages
MVC
- Model View controller
- Model - Subject/Publisher
- View - Observer
from abc import ABCMeta, abstractmethod
class IObserver(metaclass = ABCMeta):
@abstractmethod
def update(self, value):
pass
def __enter__(self):
return self
@abstractmethod
def __exit__(self, exc_type, exc_value, traceback):
pass
class IPublisher(metaclass = ABCMeta):
_observers = set()
def attach(self, observer):
if not isinstance(observer, IObserver):
raise TypeError('Observer not derived from IObserver')
self._observers |= {observer}
def detach(self, observer):
self._observers -= {observer}
def notify(self, msg=None):
for observer in self._observers:
if msg is None:
observer.update()
else:
observer.update(msg)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self._observers.clear()
class KPIs(IPublisher):
_open_tickets = -1
_closed_tickets = -1
_new_tickets = -1
@property
def open_tickets(self):
return self._open_tickets
@property
def closed_tickets(self):
return self._closed_tickets
@property
def new_tickets(self):
return self._new_tickets
def set_kpis(self, open_tickets, closed_tickets, new_tickets, msg=None):
self._open_tickets = open_tickets
self._closed_tickets = closed_tickets
self._new_tickets = new_tickets
self.notify(msg)
class CurrentKPIs(IObserver):
open_tickets = -1
closed_tickets = -1
new_tickets = -1
def __init__(self, kpis):
self._kpis = kpis
kpis.attach(self)
def update(self, msg=None):
self.open_tickets = self._kpis.open_tickets
self.closed_tickets = self._kpis.closed_tickets
self.new_tickets = self._kpis.new_tickets
self.display(msg)
def display(self, msg=None):
print(f'Current KPIs ({msg}):\nOpen: {self.open_tickets}'
f'\nClosed: {self.closed_tickets}\nNew: {self.new_tickets}\n')
def __exit__(self, exc_type, exc_value, traceback):
self._kpis.detach(self)
class ForecastKPIs(IObserver):
open_tickets = -1
closed_tickets = -1
new_tickets = -1
def __init__(self, kpis):
self._kpis = kpis
kpis.attach(self)
def update(self, msg=None):
self.open_tickets = self._kpis.open_tickets
self.closed_tickets = self._kpis.closed_tickets
self.new_tickets = self._kpis.new_tickets
self.display(msg)
def display(self, msg):
print(f'Forecast KPIs ({msg}):\nOpen: {self.open_tickets}'
f'\nClosed: {self.closed_tickets}\nNew: {self.new_tickets}\n')
def __exit__(self, exc_type, exc_value, traceback):
self._kpis.detach(self)
with KPIs() as kpis:
with CurrentKPIs(kpis) as currKPIs, ForecastKPIs(kpis):
kpis.set_kpis(10, 20, 30, 'Good Performance')
kpis.detach(currKPIs)
print("=========================\nAfter Detaching\n=========================\n")
kpis.set_kpis(1, 2, 3, 'Critical Performance')
Current KPIs (Good Performance): Open: 10 Closed: 20 New: 30 Forecast KPIs (Good Performance): Open: 10 Closed: 20 New: 30 ========================= After Detaching ========================= Forecast KPIs (Critical Performance): Open: 1 Closed: 2 New: 3
kpis.set_kpis(100, 120, 160) # No more notifications fired
class IObservable(metaclass = ABCMeta):
_observers = set()
def subscribe(self, observer):
self._observers |= {observer}
def unsubscribe(self, observer):
self._observers -= {observer}
def emit(self, val):
for observer in self._observers:
observer(val)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self._observers.clear()
class NewKPIs(IObservable):
_open_tickets = -1
_closed_tickets = -1
_new_tickets = -1
def set_kpis(self, open_tickets, closed_tickets, new_tickets):
self._open_tickets = open_tickets
self._closed_tickets = closed_tickets
self._new_tickets = new_tickets
self.emit((self._open_tickets, self._closed_tickets, self._new_tickets))
def currKPI(val):
x, y, z = val
print(f'Current KPIs:\nOpen: {x}\nClosed: {y}\nNew: {z}\n')
def foreKPI(val):
x, y, z = val
print(f'Forecast KPIs:\nOpen: {x}\nClosed: {y}\nNew: {z}\n')
with NewKPIs() as newKPIs:
newKPIs.subscribe(currKPI)
newKPIs.subscribe(foreKPI)
newKPIs.set_kpis(1, 2, 3)
newKPIs.unsubscribe(currKPI)
print("=========================\nAfter Detaching\n=========================\n")
newKPIs.set_kpis(10, 20, 30)
Forecast KPIs: Open: 1 Closed: 2 New: 3 Current KPIs: Open: 1 Closed: 2 New: 3 ========================= After Detaching ========================= Forecast KPIs: Open: 10 Closed: 20 New: 30