Observer design pattern
#Code

Observer design pattern

Applikationen drehen sich um Daten und ihre Manipulation. Die Daten in einer Applikation sind das wichtige für einen Kunden und sollten es auch für uns als Entwlickler sein. Um es in den Worten von Joe MacMillan von "halt and catch fire" zu sagen:

Software is not the thing, it is the thing that leads us to the thing

Es sollte also nicht überraschen, dass das heute vorgestellte Design pattern sich um Daten und -austausch dreht.

Jeder, der weiß wie ein Zeitungsabbonament funktioniert versteht das Observer design pattern berreits.

Don't call us, we'll call you

Das observer design patten erlaubt es Objekten sich auf Zustandsänderungen zu registrieren und über diese informiert zu werden. (Hier sei daran erinnert, dass der Zustand eines Objektes als der Wert seiner Attribute zu einer bestimmten Zeit definiert ist).

Ein Prinz wurde geboren

Dieser Artikel wäre kein Artikel über design pattern, wenn er kein Tierbeispiel enthalten würde.

Nehmen wir an, der König der Tierwelt wollte alle Tiere über Änderungen in der königlichen Familie informieren. Als erstes müssten wir interfaces für das Observable (den Löwen) und Observer (alle anderen Tiere) definieren:

interface Observable {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

interface Observer {
    void update();
}

Die Aufgabe des Observable ist es, die Methode notifyObservers aufzurufen, sobald sich der state des Objektes geändert hat. notifyObservers wiederrum ruft die update methode der Observer auf.

Sehen wir uns einmal an, wie der Löwe implementiert werden würde:

class Lion implements Observable {

  /**
   * List of all observers that want to be informed
   */
    private List<Observer> observers = new List<>();

  /**
   * One attribute that defines the state of this lion
   */
    private
List<Lion> children = new List<>();

    @Override
    public void addObserver(Observer observer) {
        this.observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        this.observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : this.observers) {
            observer.update(this);
        }
    }

  /**
   * Whenever a new child is added to the royal family,
   * we also want to notify all of the observers about
   * the change
   */
    public void addChild(Lion child) {
        this.children.add(child);
        this.notifyObservers();
    }
}

Die Schönheit dieses Design pattern liegt in dem Fakt, dass der Löwe keine Informationen über die Observer haben muss. Für ihn ist lediglich interessant, dass sie das Observable interface implementieren.

Events: Mehr als Observer

Observer sind an den Zustand / state eines konkreten Objektes gebunden. Events werden häufig an Stelle von Observern verwendet, da diese es erlauben über Dinge zu informieren, die nicht an ein bestimmtes Objekt gebunden sind. In machen Fällen erlauben sie es sogar, Daten zu modifizieren, bevor eine Operation ausgeführt wird.

Beispiele

JavaScript

JavaScript's EventEmitter ist ein Beispiel für die Stärken von Events. Diese Events erlauben es über bestimmte Aktionen (wie z.b. einen Klick auf einen Button oder neue Date von einem Server) zu warten. Weil Funktionen in JavaSript als Argumente übergeben werden können, fühlt sich diese Designpattern besonders natürlich an

// socket.io
var socket = io('http://localhost:3999');
socket.on('data', data => doSomething(data))

Android

In der Android-App Entwicklung werden Observer oft eingesetzt - vorallem für Nutzerinteraktion. Es existiert keine Weise mit der Android Nutzeroberfläche zu interagieren, ohne Observer zu nutzen. Interessanterweise besitzt Android nicht ein einzelnes Observer interface, das alle Observer gleich macht, sondern ein interface für jedes Event / Observable. Dies erlaubt einfachere Fehlersuche und die Verwendung des aktuellen Objektes als Handler für viele verschiedene Events.

spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        setNoiseGenerator(position);
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {}
});

TYPO3

TYPO3 nutzt signal slots, welche JavaScript Events sehr ähnlich sind (jedoch in einer PHP Umgebung). Diese Signale werden oft vor dem Ausführen einer Aktion ausgeführt und erlauben das modifizieren der Daten für diese Aktion.

ext_localconf.php


/** @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher */
$signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\SignalSlot\Dispatcher');
$signalSlotDispatcher->connect(
    'In2code\Powermail\Domain\Service\SendMailService',
    'sendTemplateEmailBeforeSend',
    'NIMIUS\Testext\Util\DebugUtil',
    'routeMailBack',
    FALSE
);

Classes/Util/DebugUtil.php

namespace NIMIUS\Testext\Util;
use TYPO3\CMS\Core\Mail\MailMessage;

class DebugUtil {

    /**
     * Reroutes all outgoing powermail messages to a test adress
     * @param MailMessage $message
     * @return void
     */
    public routMailBack(MailMessage $message) {
        $message->setTo([ 'testing@nimius.net' => 'NIMIUS Testing' ]);
    }
}