This is a Python implementation of the observer pattern described by Gamma et. al. It defines a one-to many dependency between objects so that when one object changes state, all its dependents (i.e. observers) are notified and updated automatically.
My adaptation gets rid of the need to use specific functions to set the data (and to call Notify) and allows you to be notified for ANY attribute you set. It is possible to specify a list of attributes which should not trigger a notification. In case you need the opposite, it is very easy to invert the behavior of the code.
The example should output:
Creating data1 without notification for attrs name & surname
Creating data2 without notification for attr age
Setting data1.name=Heather - Notification unnecessary
Setting data1.num=333 - Notification expected
Observer1: Subject Heather has updated attr num to 333
Setting data2.name=Molly - Notification expected
Observer2: Subject Molly has updated attr name to Molly
Setting data2.age=28 - Notification unnecessary
Setting data2.eyecolor=blue - Notification expected
Observer2: Subject Molly has updated attr eyecolor to blue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | import copy
class Subject:
def __init__(self, excluded):
# excluded is a list of attribute names
# who should be notified
self.__dict__['_observers'] = []
# which attrs should not trigger a notification
# obviously it can be done the other way round if needed
# i.e.which attrs trigger a notification
self.__dict__['_excluded_attrs'] = copy.copy(excluded)
def __setattr__(self, attrname, value):
# set the attribute
self.__dict__[attrname] = value
# if the attribute triggers a notification
if not attrname in self._excluded_attrs:
# notify observers
self.notify(attrname, value)
def attach(self, observer):
if not observer in self._observers:
self._observers.append(observer)
def detach(self, observer):
try:
self._observers.remove(observer)
except ValueError:
pass
def notify(self, attrname, value):
for observer in self._observers:
observer.update(self, attrname, value)
# Example usage
class Data(Subject):
def __init__(self, excluded=[]):
# pass up the attrs to be excluded from notification
Subject.__init__(self, excluded)
class Observer1:
def update(self, subject, attrname, value):
print 'Observer1: Subject %s has updated attr %s to %s' % (subject.name, attrname, value)
class Observer2:
def update(self, subject, attrname, value):
print 'Observer2: Subject %s has updated attr %s to %s' % (subject.name, attrname, value)
# Example
def main():
print "Creating data1 without notification for attrs name & surname"
data1 = Data(excluded=['name','surname'])
print "Creating data2 without notification for attr age"
data2 = Data(excluded=['age'])
obs1 = Observer1()
data1.attach(obs1)
data1.attach(obs1)
obs2 = Observer2()
data2.attach(obs2)
data2.attach(obs2)
# now try it
# you can set any attribute directly
print "Setting data1.name=Heather - Notification unnecessary"
data1.name = 'Heather'
print "Setting data1.num=333 - Notification expected"
data1.num = 333;
print "Setting data2.name=Molly - Notification expected"
data2.name = 'Molly'
print "Setting data2.age=28 - Notification unnecessary"
data2.age = 28
print "Setting data2.eyecolor=blue - Notification expected"
data2.eyecolor='blue';
if __name__ == '__main__':
main()
|
The classes shown are meant for subclassing. The subject class is a bit modified in relation to the pattern described by Gamma et. al. I have removed (from Notify) the optional functionality to avoid calling a specific observer.