Der lise Blog:
Einsichten, Ansichten, Aussichten

Observer-Pattern: Kein goldener Hammer!

 

In seinem Beitrag "Angular(2+) und der Einstieg in die Welt von RxJS" stellt Alexander Winnen das Observer-Pattern (Beobachter) im Vergleich zum Promise vor. Ich selber bin auch ein Fan dieses Entwurfsmusters, da es sich nicht nur für asynchrone Updates, sondern auch zum Distributieren von Daten-Updates zwischen Klassen eignet. Allerdings halte ich es für wichtig, die Grenzen dieses Entwurfsmusters zu kennen, damit aus dem Pattern kein Anti-Pattern wird.

Observer-Pattern: Änderungen abonnieren

Das Observer-Pattern folgt einem einfachen Prinzip:

  • Klasse A beinhaltet Daten, die sich ändern können (zum Beispiel der Verbindungsstatus zu einem Server)
  • Klasse B möchte über Änderungen an Objekten der Klasse A informiert werden (zum Beispiel, um diese in der GUI anzuzeigen)
  • Klasse B trägt sich in Klasse A als "Beobachter" ein ("subscribe")
  • Sobald sich ein Wert der Klasse A ändert, teilt sie dies ihren Beobachtern mit ("notifiy")
  • In Klasse B wird eine Veränderung eines Objektes von A durch den Aufruf der speziellen Callback-Funktion registriert ("update")

Verwendungszweck: Änderungen propagieren und asynchrone Schnittstellen

Neben dem "klassischen" Verwendungszweck zum Propagieren von Updates eines Objektes wird in RxJS ein weiterer Verwendungszweck eingeführt: Asynchronität. Als Ersatz zu Promisses bieten Observer den Vorteil, dass Events mehrfach ausgelöst werden können. Dies erklärt Alexander Winnen in seinem Beitrag zu RxJS.

Observer: einfach umzusetzen... 

Das Gute am Observer-Pattern ist seine Einfachheit:

  1. Beobachtbare Objekte halten eine Liste ihrer Beobachter vor und melden diesen ihre Veränderungen.
  2. Beobachter registrieren sich und müssen einen Callback anbieten, mit dem sie über Updates Bescheid bekommen.

Mehr ist nicht zu tun um ein System zu bauen, in dem Änderungen an alle notwendigen Stellen propagiert werden. Fertig?

... schwer zu meistern!

Im Kleinen ist das Observer-Pattern überschaubar. Wird das System größer, verteilen sich die Abläufe und es wird komplizierter. Wenn zum Beispiel ein Beobachter B das Update von Objekt A empfängt und seinerseits dann eigene Beobachter der Klasse C benachrichtigt. Ich würde dies ab einer gewissen Größe des Benachrichtigungsnetzes als einen Schwarm bezeichnen.

Schwarmverhalten ist ein cooles Thema in der Softwareentwicklung - aber furchtbar zu debuggen und zu verstehen.
Dieses Netz von Beobachtern folgt eigenen (aber einfachen) Regeln und bildet ein paralleles System zu den bereits vorhandenen Systemen in der Software (Klassenmodell, Architekturschichten, Hardware- oder Service-Infrastruktur):

  • Wer benachrichtigt wen und wann? 
  • Welche Ketten von Events entstehen dabei (hier empfehle ich zur Analyse Petri-Netze)?
  • Gibt es Zyklen oder doppelte Events? 
  • Wann wird deabonniert?
  • Müssen Beobachter mit unterschiedlicher Priorität benachrichtigt werden oder sind alle gleich? 
  • Sollen Benachrichtigungen seriell oder in separaten Threads stattfinden? 

Diese und viele andere Fragen gilt es, zu klären und Regeln zu definieren. Der Entwickler oder Architekt muss ein deterministisches und nachvollziehbares Benachrichtigungsnetz sicherstellen.

Was sagen andere zu dem Thema?

"Die Entwickler, die damals entwickelten [nach der Veröffentlichung des Buches der GOF, Anm. d. Red.] erinnern sich daran, dass für ein paar Monate jeder dachte, dass das Observer-Pattern mega cool war. Wir sahen viele Observer-basierte Designs. Das stoppte, da diese Designs zu indirekt waren" - Robert C. Martin, Quelle

Schon vor 20 Jahren waren Observer bekannt, aber konnten sich nicht lange durchsetzen. Sie sind nicht geeignet für größere Designs.

"Und wie so oft hat C++ eine over-engineerte Lösung namens Boost Signal ans Slots[...]. Sie war so schlecht, dass sie eine zweite Library erstellen mussten." - Alan Ning, Quelle

Das Observer-Pattern birgt die Gefahr, dass man es überstrapaziert. So ist es auch den Entwicklern der berühmten Boost-Bibliothek passiert.

"Das Observer-Pattern [...] verletzte eine beeindruckende Liste von wichtigen Prinzipien der Softwareentwicklung: Seiteneffekte, Verkapselung, Kombinierbarkeit, SRP, Trennung der Zuständigkeiten, Skalierbarkeit, Einheitlichkeit, Abstraktion, Ressourcenverwaltung, semantische Distanz." - Jeff Axelrod, Quelle

Jeff Axelrod fasst ein Paper von Ingo Maier, Tiark Rompf und Martin Odersky aus dem Jahre 2010 zusammen. Sie alle schlagen vor, das Observer-Pattern auf die schwarze Liste zu setzen. Es verletzt zu viele etablierte Kozepte der Softwareentwicklung.

Was sollen wir jetzt tun?

Ich bin dagegen, das Observer-Pattern zu vermeiden oder es gar als Anti-Pattern zu deklarieren. 
Aufgrund seiner Einfachheit ist es für kleine Projekte gut einsetzbar. Es hat klare Vorteile gegenüber einem Promise.
Die Botschaft sollte aber dennoch klar sein: Der Einsatz sollte streng limitiert geschehen. Schaltet man Observer hintereinander oder transformiert einen Observer in einen anderen, sollte Schluss sein. Irgendwann befindet man sich sonst in der Callback-Hölle.

 

Foto: Algonga/Shutterstock

Diesen Artikel weiterempfehlen

 Teilen  Teilen  Teilen  Teilen