Das Beobachter Pattern

Das Entwurfsmuster Beobachter (engl. Observer pattern), auch Publisher/Subscriber Pattern genannt, ist ein objekt-orientiertes Struktur- bzw. Verhaltensmuster, das die Beziehung und Interaktion zwischen mehreren Objekten beschreibt, die Ereignisse generieren und verarbeiten. Es wird verwendet, um Objektbeziehungen zu entkoppeln und ist oft Grundlage des Event-Handlings in objekt-orienterten Anwendungen.

Der folgende Vortrag stellt das Beobachter-Pattern vor und bietet eine Beispiel-Implementierung in Python. Der Vortrag wurde bei einem der Treffen von pyCologne gehalten.

Beispiel-Implementierung

   1 """Class implementing Publisher/Subscriber object pattern.
   2 
   3 Also called Observer pattern. The Observer/Subscriber class is not included
   4 because it is just an interface, requiring only a notify() method. See the 
   5 StockViewer class in the test code at the end of the module for a minimal 
   6 example.
   7 """
   8 
   9 __all__ = ['UnsupportedEventError', 'Event', 'Publisher']
  10 
  11 
  12 __author__    = "Christopher Arndt"
  13 __version__   = "1.0.1"
  14 __revision__  = "$Rev: 199 $"
  15 __date__      = "$Date: 2008-01-11 16:15:28 +0100 (Fr, 11 Jan 2008) $"
  16 __copyright__ = "MIT license"
  17 
  18 # standard library modules
  19 import sys
  20 from weakref import WeakKeyDictionary
  21 
  22 if sys.version_info[:2] < (2,4):
  23     try:
  24         from sets import Set as set
  25     except ImportError:
  26         raise NotImplemented, "Needs either Python >= 2.4 or the 'sets' module."
  27 
  28 class UnsupportedEventError(Exception):
  29     pass
  30 
  31 class Event(object):
  32     name = "Generic event"
  33 
  34     def __init__(self, **state):
  35         self.state = state
  36 
  37     def __str__(self):
  38         state = ", ".join(["%s: %r" % (k,v)
  39             for k,v in sorted(self.state) if not callable(k)])
  40         return "%s (%s)\n" % (self.name, state)
  41 
  42 class Publisher(object):
  43     """Publisher class with support for push/pull method and event broking.
  44     
  45     Can be used as a subclass for every object that generates signals
  46     to which observers can subscribe.
  47     
  48     Can also be used as an Event Broker, where Publishers register their 
  49     signals and Subscribers subscribe to them.
  50     
  51     Subscribers may get the state of the Publisher by calling the ``get_state``
  52     method of the Publisher (pull method - only makes sense when every event
  53     generating object implements the Publisher interface) or the Publishers
  54     should attach their current state to the event object, which is sent
  55     to subscribers (push method).
  56     """
  57 
  58     def __init__(self):
  59         self.subscribers = {}
  60         self.events = set([Event])
  61         self.debug = False
  62 
  63     def subscribe(self, subscriber, events=None):
  64         """Subscribe ``subscriber`` to be notified if one of ``events`` occurs.
  65         
  66         ``subscriber`` must be an object with a ``notify`` method, which 
  67         accepts one argument, the event signalled.
  68         
  69         ``events`` should be a list of ``Event`` subclasses class or instances 
  70         thereof. When subscribing to the generic ``Event`` class (or omitting
  71         the ``events`` argument), the subscriber will get notified of all 
  72         events.
  73         """
  74 
  75         # observer must have 'notify()' method
  76         assert hasattr(subscriber, 'notify')
  77         # 'notify()' must accept at least two arguments
  78         assert subscriber.notify.func_code.co_argcount >= 2
  79         if events is None:
  80             events = [Event]
  81         for event in events:
  82             if isinstance(event, Event):
  83                 event = event.__class__
  84             if event not in self.events:
  85                 raise UnsupportedEventError, "Event '%s' not supported." % event
  86             self.subscribers.setdefault(event, WeakKeyDictionary()
  87                 )[subscriber] = True
  88 
  89     def unsubscribe(self, subscriber, events=None):
  90         """Unscubscribe ``subscriber`` from being notified of ``events``.
  91         
  92         ``events`` should be a list of ``Event`` subclasses class or instances 
  93         thereof. When omitting the ``events`` argument, the subscriber will 
  94         get unsubscribed from all events.
  95         """
  96 
  97         if isinstance(event, Event):
  98             event = event.__class__
  99         if events is None:
 100             events = list(self.events)
 101         for event in events:
 102             try:
 103                 del self.subscribers[event][subscriber]
 104             except KeyError:
 105                 pass
 106 
 107     def notify_subscribers(self, event):
 108         """Notify all subscribers of an event.
 109         
 110         Basically just calls ``notify(event)`` on every object subscribed
 111         to this event or to the generic ``Event``. The order in which 
 112         subscribers are notified is not defined.
 113         """
 114 
 115         if event.__class__ not in self.events:
 116             raise UnsupportedEventError, "Event '%s' not supported." % event
 117         # build list of subscribers to given and catch-all event
 118         subscribers = dict(self.subscribers.get(event.__class__, {}))
 119         subscribers.update(self.subscribers.get(Event, {}))
 120         if not getattr(event, 'publisher', None):
 121             event.publisher = self
 122         for subscriber in subscribers:
 123             if self.debug and getattr(event, '_log', True):
 124                 print event
 125             subscriber.notify(event)
 126 
 127     def update(self, event):
 128         """Signal event.
 129         
 130         To be called by Publishers in a distributed architecture when this
 131         class acts as an event broker.
 132         
 133         The default implementation just calls ``self.notify_subscribers(event)``.
 134         Subclasses might want to overwrite this method to filter/alter events
 135         or do some checks on them before publishing.
 136         """
 137 
 138         self.notify_subscribers(event)
 139 
 140     def get_supported_events(self):
 141         """Return list of registered events."""
 142 
 143         return self.events
 144 
 145     def add_event(self, event):
 146         """Register new event.
 147         
 148         ``event`` may be a subclass of ``Event`` or an instance thereof.
 149         """
 150 
 151         if isinstance(event, Event):
 152             event = event.__class__
 153         self.events.add(event)
 154 
 155     def remove_event(self, event):
 156         """Unregister an event.
 157         
 158         ``event`` may be a subclass of ``Event`` or an instance thereof.
 159         """
 160 
 161         if isinstance(event, Event):
 162             event = event.__class__
 163         if event is Event:
 164             raise ValueError, "Can't remove catch-all event."
 165         try:
 166             del self.subscribers[event]
 167         except: pass
 168         try:
 169             self.events.remove(event)
 170         except: pass
 171 
 172     def get_state(self, *args):
 173         """Return current state of object.
 174         
 175         To be called by subscribers on notify().
 176         """
 177 
 178         raise NotImplemented, "Method must be implemented by subclass."
 179 
 180 
 181 def _test():
 182     """Module test code."""
 183 
 184     class AddGoodsEvent(Event):
 185         name = 'Goods added event'
 186 
 187     class RemoveGoodsEvent(Event):
 188         name = 'Goods removed event'
 189 
 190     # publisher
 191     class Stock(Publisher):
 192 
 193         def __init__(self):
 194             super(Stock, self).__init__()
 195             self.stock = {}
 196             self.add_event(AddGoodsEvent)
 197             self.add_event(RemoveGoodsEvent)
 198 
 199         def add_goods(self, what, amount):
 200             try:
 201                 self.stock[what] += amount
 202             except KeyError:
 203                 self.stock[what] = amount
 204             self.notify_subscribers(AddGoodsEvent())
 205 
 206         def remove_goods(self, what, amount):
 207             try:
 208                 stock = self.stock[what]
 209                 if amount > stock:
 210                     amount = stock
 211                 self.stock[what] -= amount
 212                 stock = self.stock
 213             except KeyError:
 214                 stock = 0
 215             self.notify_subscribers(RemoveGoodsEvent())
 216             return self.stock
 217 
 218         def get_state(self):
 219             return self.stock
 220 
 221     # subscriber
 222     class StockViewer(object):
 223 
 224         def notify(self, event):
 225             if isinstance(event, AddGoodsEvent):
 226                 print "Goods have been added to the stock!"
 227             elif isinstance(event, RemoveGoodsEvent):
 228                 print "Goods have been taken from the stock!"
 229             goods = event.publisher.get_state()
 230             print "Stock now:", goods
 231             for good, amount in goods.items():
 232                 if amount <= 5:
 233                     print "Running low on %s!" % good
 234             print
 235 
 236     stock = Stock()
 237     stockviewer = StockViewer()
 238     # by default, get notified of all events
 239     #stock.subscribe(stockviewer)
 240     stock.subscribe(stockviewer, [AddGoodsEvent, RemoveGoodsEvent])
 241 
 242     stock.add_goods('apples', 10)
 243     stock.add_goods('bananas', 30)
 244     stock.add_goods('cherries', 100)
 245 
 246     stock.remove_goods('apples', 3)
 247     stock.remove_goods('bananas', 25)
 248 
 249 if __name__ == '__main__':
 250     _test()

Das Beobachter Pattern (last edited 2009-06-17 16:14:17 by localhost)