software_patterns.notification   A
last analyzed

Complexity

Total Complexity 12

Size/Duplication

Total Lines 162
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 12
eloc 41
dl 0
loc 162
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A SubjectInterface.notify() 0 4 1
A ObserverInterface.update() 0 4 1
A SubjectInterface.attach() 0 4 1
A Subject.__init__() 0 3 1
A Subject.detach() 0 2 1
A Subject.notify() 0 3 2
A Subject.add() 0 3 1
A Subject.state() 0 8 1
A Subject.attach() 0 2 1
A SubjectInterface.detach() 0 4 1
1
"""Notification-Listener (aka subject-observer) software design pattern.
2
3
Simple implementation of the subject/observers (broadcast/listeners) pattern,
4
exposed as python classes.
5
6
This is a Behavioural Pattern which can be used when you want one or more
7
components to be notified and react accordingly, when 'something happens'.
8
9
One entity, known as Subject, is responsible to send out a notification and each
10
entity "subscribed" to the Subject receives it and reacts.
11
Each subscribed entity is known as a Listener or Observer.
12
13
The idea is that the Subject is agnostic of its Observers implementation and the
14
client code can "attach" or "detach" (subscribe/unsubscribe) as many of them at
15
runtime.
16
17
This module provides the Observer class, serving as the interface that needs to
18
be implemented by concrete classes; the update method needs to be overrode.
19
Concrete Observers react to the notifications/updates issued by the Subject they
20
had been attached/subscribed to.
21
22
This module also provides the concrete Subject class, serving with methods to
23
subscribe/unsubscribe (attach/detach) observers and also with a method to
24
"notify" all Observers.
25
"""
26
27
from abc import ABC, abstractmethod
28
from typing import Generic, List, TypeVar, Union
29
30
__all__ = ['Subject', 'Observer']
31
32
33
StateType = TypeVar('StateType')
34
StateVariableType = Union[StateType, None]
35
36
37
class ObserverInterface(ABC):
38
    """The Observer interface declares the update method, used by subjects.
39
40
    Enables objects to act as "event" listeners; react to "notifications"
41
    by executing specific (event) handling logic.
42
    """
43
44
    @abstractmethod
45
    def update(self, *args, **kwargs) -> None:
46
        """Receive an update (from a subject); handle an event notification."""
47
        raise NotImplementedError
48
49
50
class SubjectInterface(ABC):
51
    """The Subject interface declares a set of methods for managing subscribers.
52
53
    Enables objects to act as subjects that broadcast events" by notifying
54
    all subscribed observers/listeners.
55
    """
56
57
    @abstractmethod
58
    def attach(self, observer: ObserverInterface) -> None:
59
        """Attach an observer to the subject; subscribe the observer."""
60
        raise NotImplementedError
61
62
    @abstractmethod
63
    def detach(self, observer: ObserverInterface) -> None:
64
        """Detach an observer from the subject; unsubscribe the observer."""
65
        raise NotImplementedError
66
67
    @abstractmethod
68
    def notify(self) -> None:
69
        """Notify all observers about an event."""
70
        raise NotImplementedError
71
72
73
class Observer(ObserverInterface, ABC):
74
    pass
75
76
77
class Subject(SubjectInterface, Generic[StateType]):
78
    """Concrete Subject which owns an important state and notifies observers.
79
80
    The subject can be used to build the data encapsulating the event being
81
    broadcasted.
82
83
    Both the _state and _observers attributes have a simple implementation,
84
    but can be overrode to accommodate for more complex scenarios.
85
86
    The observers/subscribers are implemented as a python list.
87
    In more complex scenarios, the list of subscribers can
88
    be stored more comprehensively (categorized by event type, etc.).
89
90
    The subscription management methods provided are 'attach', 'detach' (as in
91
    the SubjectInterface) and 'add', which attached multiple observers at once.
92
93
    Example:
94
95
        >>> from software_patterns import Subject, Observer
96
97
        >>> broadcaster = Subject()
98
99
        >>> class ObserverTypeA(Observer):
100
        ...  def update(self, *args, **kwargs):
101
        ...   event = args[0].state
102
        ...   print(f'observer-type-a reacts to event {event}')
103
104
        >>> class ObserverTypeB(Observer):
105
        ...  def update(self, *args, **kwargs):
106
        ...   event = args[0].state
107
        ...   print(f'observer-type-b reacts to event {event}')
108
109
        >>> subscriber_1 = ObserverTypeA()
110
        >>> subscriber_2 = ObserverTypeB()
111
112
        >>> broadcaster.add(subscriber_2, subscriber_1)
113
114
        >>> broadcaster.state = 'event-object-A'
115
116
        >>> broadcaster.notify()
117
        observer-type-b reacts to event event-object-A
118
        observer-type-a reacts to event event-object-A
119
120
        >>> broadcaster.detach(subscriber_2)
121
122
        >>> broadcaster.state = 'event-object-B'
123
        >>> broadcaster.notify()
124
        observer-type-a reacts to event event-object-B
125
    """
126
127
    def __init__(self, *args, **kwargs):
128
        self._observers: List[ObserverInterface] = []
129
        self._state = StateVariableType
130
131
    def attach(self, observer: ObserverInterface) -> None:
132
        self._observers.append(observer)
133
134
    def detach(self, observer: ObserverInterface) -> None:
135
        self._observers.remove(observer)
136
137
    def notify(self) -> None:
138
        for observer in self._observers:
139
            observer.update(self)
140
141
    def add(self, *observers):
142
        """Subscribe multiple observers at once."""
143
        self._observers.extend(list(observers))
144
145
    @property
146
    def state(self) -> StateVariableType:
147
        """Get the state of the Subject.
148
149
        Returns:
150
            StateType: the object representing the current state of the Subject
151
        """
152
        return self._state
153
154
    @state.setter
155
    def state(self, state: StateType):
156
        """Set the state of the Subject.
157
158
        Args:
159
            state (StateType): the state object
160
        """
161
        self._state = state
162