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

The "broadcaster" and "broker" modules enable loose coupling between objects in a running application.

Python, 120 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
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#####################################################################
# broadcaster.py
#

__all__ = ['Register', 'Broadcast',
            'CurrentSource', 'CurrentTitle', 'CurrentData']

listeners = {}
currentSources = []
currentTitles = []
currentData = []

def Register(listener, arguments=(), source=None, title=None):
    if not listeners.has_key((source, title)):
        listeners[(source, title)] = []

    listeners[(source, title)].append((listener, arguments))

def Broadcast(source, title, data={}):
    currentSources.append(source)
    currentTitles.append(title)
    currentData.append(data)

    listenerList = listeners.get((source, title), [])[:]
    if source != None:
        listenerList += listeners.get((None, title), [])
    if title != None:
        listenerList += listeners.get((source, None), [])

    for listener, arguments in listenerList:
        apply(listener, arguments)

    currentSources.pop()
    currentTitles.pop()
    currentData.pop()

def CurrentSource():
    return currentSources[-1]

def CurrentTitle():
    return currentTitles[-1]

def CurrentData():
    return currentData[-1]


#####################################################################
# broker.py
#

__all__ = ['Register', 'Request',
            'CurrentTitle', 'CurrentData']

providers = {}
currentTitles = []
currentData = []

def Register(title, provider, arguments=()):
    assert not providers.has_key(title)
    providers[title] = (provider, arguments)

def Request(title, data={}):
    currentTitles.append(title)
    currentData.append(data)

    result = apply(apply, providers.get(title))

    currentTitles.pop()
    currentData.pop()

    return result

def CurrentTitle():
    return currentTitles[-1]

def CurrentData():
    return currentData[-1]


#####################################################################
# sample.py
#

from __future__ import nested_scopes

import broadcaster
import broker

class UserSettings:
    def __init__(self):
        self.preferredLanguage = 'English'
        # The use of lambda here provides a simple wrapper around
        # the value being provided. Every time the value is requested
        # the variable will be reevaluated by the lambda function.
        # Note the dependence on nested scopes.
        broker.Register('Preferred Language', lambda: self.preferredLanguage)

        self.preferredSkin = 'Cool Blue Skin'
        broker.Register('Preferred Skin', lambda: self.preferredSkin)

    def ChangePreferredSkinTo(self, preferredSkin):
        self.preferredSkin = preferredSkin
        broadcaster.Broadcast('Preferred Skin', 'Changed')

    def ChangePreferredLanguageTo(self, preferredLanguage):
        self.preferredLanguage = preferredLanguage
        broadcaster.Broadcast('Preferred Language', 'Changed')

def ChangeSkin():
    print 'Changing to', broker.Request('Preferred Skin')

def ChangeLanguage():
    print 'Changing to', broker.Request('Preferred Language')

broadcaster.Register(ChangeSkin, source='Preferred Skin', title='Changed')
broadcaster.Register(ChangeLanguage, source='Preferred Language', title='Changed')

userSettings = UserSettings()
userSettings.ChangePreferredSkinTo('Bright Green Skin')
userSettings.ChangePreferredSkinTo('French')

These modules are particularly useful in GUI applications where they help to shield application logic from UI changes.

Broadcasting is essentially equivalent to multiplexed function calls where the caller does not need to know the interface of the called function(s). A broadcaster can optionally supply data for the listeners to consume. For example if an application is about to exit, it can broadcast a message to that effect and any interested objects can do their finalization. Another example is a UI control which can broadcast a message whenever its state changes so that other objects can respond appropriately.

The broker enables the retrieval of named data even when the source of the data is not known. For example a UI control (such as an edit box) can register itself as a data provider with the broker and then any code in the application can retrieve the control’s value with no knowledge of how or where the value is stored. This avoids two potential pitfalls – (1) storing data in multiple locations and requiring extra logic to keep those locations in sync and (2) proliferating the dependency upon the control’s API.

The broker and broadcaster can work together quite nicely. Take, for example, an edit box that is used for entering a date. Whenever its value changes, it can broadcast a message indicating that the entered date has changed. Anything depending upon that date can respond to that message by asking the broker for the current value. Later the edit box can be replaced by a calendar control. As long as the new control broadcasts the same messages and provides the same data through the broker, no other code should need to be changed.

This idiom is “thread hostile.” Even if access to the module level variables was properly controlled, this style of programming is tailor made for deadlocks / race conditions. Please consider the impact carefully before using this from multiple threads.

It would be interesting to extend this idiom for use in inter-application communication (or even for use across a network).

2 comments

Drew Perttula 17 years, 11 months ago  # | flag

also see PyDispatcher. readers will likely be interested in http://pydispatcher.sourceforge.net/ which solves a similar problem.

I use pydispatcher often, but I have not used this recipe so I can't comment on which is better.

a 11 years, 7 months ago  # | flag

apply() is deprecated. apply(listener, arguments) should be changed to listener(*arguments). result = apply(apply, providers.get(title)) should be changed to provider, arguments = providers.get(title); result = provider(*arguments)