Welcome, guest | Sign In | My Account | Store | Cart

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

Python, 81 lines
 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.