mirror of
https://github.com/jtgans/g13gui.git
synced 2025-06-20 00:14:09 -04:00
g13gui: Add in an observer pattern
We'll need this as complexity of the GUI grows. This allows us to queue up change notifications and pass them to observing objects more cleanly.
This commit is contained in:
parent
bac31a772a
commit
eb816c8acb
84
g13gui/g13gui/observer.py
Normal file
84
g13gui/g13gui/observer.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeType(Enum):
|
||||||
|
ADD = 0
|
||||||
|
REMOVE = 1
|
||||||
|
MODIFY = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Observer(object):
|
||||||
|
"""Simple interface class to handle Observer-style notifications"""
|
||||||
|
|
||||||
|
def onSubjectChanged(self, subject, changeType, key, changeData):
|
||||||
|
"""Event handler for observer notifications.
|
||||||
|
|
||||||
|
Each subclass of Observer MUST override this method. There is no default
|
||||||
|
method for handling events of this nature.
|
||||||
|
|
||||||
|
subject[object]: the subject object that sent the event notifying something
|
||||||
|
changed in its data model.
|
||||||
|
changeType[ChangeType]: the type of change that occurred.
|
||||||
|
key[string]: a required name for what field changed.
|
||||||
|
whatChanged[object]: an optional context-dependent object, dict, or
|
||||||
|
None, specifying what changed. In the case of an ADD or MODIFY,
|
||||||
|
whatChanged should be the new data. In the case of a DELETE, it should
|
||||||
|
be the old data (or None).
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(
|
||||||
|
"%s did not override Observer#onSubjectChanged!" % (type(self)))
|
||||||
|
|
||||||
|
|
||||||
|
class Subject(object):
|
||||||
|
"""Simple class to handle the subject-side of the Observer pattern."""
|
||||||
|
|
||||||
|
def registerObserver(self, observer):
|
||||||
|
"""Registers an Observer class as an observer of this object"""
|
||||||
|
if '_observers' not in self.__dict__:
|
||||||
|
self._observers = {observer}
|
||||||
|
else:
|
||||||
|
self._observers.add(observer)
|
||||||
|
|
||||||
|
def removeObserver(self, observer):
|
||||||
|
"""Removes an observer from this object"""
|
||||||
|
if '_observers' in self.__dict__:
|
||||||
|
if observer in self._observers:
|
||||||
|
self._observers.discard(observer)
|
||||||
|
|
||||||
|
def addChange(self, type, key, whatChanged=None):
|
||||||
|
"""Schedules a change notification for transmitting later.
|
||||||
|
|
||||||
|
type[ChangeType]: the type of change that occurred.
|
||||||
|
key[string]: a required name for what field changed.
|
||||||
|
whatChanged[object]: an optional context-dependent object, dict, or
|
||||||
|
None, specifying what changed. In the case of an ADD or MODIFY,
|
||||||
|
whatChanged should be the new data. In the case of a DELETE, it should
|
||||||
|
be the old data (or None).
|
||||||
|
"""
|
||||||
|
if '_changes' not in self.__dict__:
|
||||||
|
self._changes = [(type, key, whatChanged)]
|
||||||
|
else:
|
||||||
|
self._changes.append((type, key, whatChanged))
|
||||||
|
|
||||||
|
def clearChanges(self):
|
||||||
|
"""Removes all scheduled changes from the change buffer."""
|
||||||
|
self._changes = []
|
||||||
|
|
||||||
|
def notifyChanged(self):
|
||||||
|
"""Notifies all observers of scheduled changes in the change buffer.
|
||||||
|
|
||||||
|
This method actually does the work of iterating through all observers
|
||||||
|
and all changes and delivering them to the Observer's onSubjectChanged
|
||||||
|
method.
|
||||||
|
|
||||||
|
It is safe to call this if there are no changes to send in the buffer,
|
||||||
|
or there are no observers to send changes to. Note that calling this
|
||||||
|
when no observers are registered will still flush the change buffer.
|
||||||
|
"""
|
||||||
|
if '_observers' in self.__dict__ and '_changes' in self.__dict__:
|
||||||
|
for observer in self._observers:
|
||||||
|
for change in self._changes:
|
||||||
|
observer.onSubjectChanged(self, *change)
|
||||||
|
self._changes = []
|
76
g13gui/g13gui/observer_tests.py
Normal file
76
g13gui/g13gui/observer_tests.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import observer
|
||||||
|
|
||||||
|
class TestObserver(observer.Observer):
|
||||||
|
def __init__(self):
|
||||||
|
self.changes = []
|
||||||
|
|
||||||
|
def onSubjectChanged(self, subject, type, key, whatChanged):
|
||||||
|
self.changes.insert(0, {
|
||||||
|
'subject': subject,
|
||||||
|
'type': type,
|
||||||
|
'key': key,
|
||||||
|
'whatChanged': whatChanged
|
||||||
|
})
|
||||||
|
|
||||||
|
def assertChangeNotified(self, subject, type, key):
|
||||||
|
change = self.changes.pop()
|
||||||
|
assert(change['subject'] == subject)
|
||||||
|
assert(change['type'] == type)
|
||||||
|
assert(change['key'] == key)
|
||||||
|
return change['whatChanged']
|
||||||
|
|
||||||
|
|
||||||
|
class TestIncorrectObserver(observer.Observer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestSubject(observer.Subject):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ObserverTestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.subject = TestSubject()
|
||||||
|
|
||||||
|
def testRegistration(self):
|
||||||
|
observer = TestObserver()
|
||||||
|
self.subject.registerObserver(observer)
|
||||||
|
assert(len(self.subject._observers) == 1)
|
||||||
|
|
||||||
|
self.subject.registerObserver(observer)
|
||||||
|
assert(len(self.subject._observers) == 1)
|
||||||
|
|
||||||
|
self.subject.removeObserver(observer)
|
||||||
|
assert(len(self.subject._observers) == 0)
|
||||||
|
|
||||||
|
self.subject.removeObserver(observer)
|
||||||
|
|
||||||
|
def testSubclassNotificationError(self):
|
||||||
|
testObserver = TestIncorrectObserver()
|
||||||
|
self.subject.addChange(observer.ChangeType.ADD, 'foo')
|
||||||
|
self.subject.registerObserver(testObserver)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.subject.notifyChanged()
|
||||||
|
except NotImplementedError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
unittest.fail('Expected NotImplementedError')
|
||||||
|
|
||||||
|
def testSubclassNotification(self):
|
||||||
|
o = TestObserver()
|
||||||
|
self.subject.registerObserver(o)
|
||||||
|
|
||||||
|
self.subject.addChange(observer.ChangeType.ADD, 'foo', 'bar')
|
||||||
|
self.subject.notifyChanged()
|
||||||
|
|
||||||
|
result = o.assertChangeNotified(
|
||||||
|
self.subject, observer.ChangeType.ADD, 'foo')
|
||||||
|
assert(result == 'bar')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
Reference in New Issue
Block a user