Einleitung

Nichts ist so beständig wie die Veränderung von Sichtweisen 🙂

Während ich an meiner library schreibe fällt mir mal wieder die Gedankenlosigkeit auf die in meinem und fast jedem anderen Projekt wieder zu finden ist. Manchmal sind Dinge so selbstverständlich Butter aufs Brot schmieren (set()) oder wie das Schreiben von get(key) und set(key, value) Methoden.
Weit gefehlt! Beim Setter ist nichts selbstverständlich! Erst nach genauerer Betrachtung (durch lesen des Codes) wir klar was set() bedeuten soll #fail.
Und wer sagt das vielleicht Butter auf dem Brot bereits war und es soll etwas mehr sein? Genau! get() #fail

Ich nehme das Beispiel des Registry Patterns (Entwurfsmuster) ohne darüber streiten zu wollen ob das zugrunde liegende Singleton pattern sinnvoll ist oder nicht): Man setzt dort etwas ab und man kann es sich jederzeit wieder holen.

Ab heute wird es bei mir nur noch reduziert oder aus Altlasten bedingten Situationen set() Methoden geben!

Warum? Es geht um eine klare Entscheidung die ich im Code treffen will und jeder der es liest soll Klarheit bekommen ohne genauer rein schauen zu müssen.

Butter aufs Brot hat zwei Bedeutungen:
– Die Scheibe kommt frisch vom Brot und ist somit im Rohzustand (Idealfall)
– Wir kennen den Status des Brotes einfach nicht, aber tun Butter drauf oder lassen es wenn wir sehen das Butter bereits drauf ist.

Die meisten denken vermutlich im Idealfall.
Den zweiten Fall kann man alle nas-lang nach in diversen Code-Quellen nachlesen: Mal wird bei Settern die Existenz einer Eigenschaft geprüft und das setzen verweigert oder stumpf ersetzt. Wer macht es richtig? Was bedeutet set() ?
set(): Sehr schwammige Kommunikation!
setIfExists() oder setIfNotExists()? addIfExists() oder addIfNotExists() ? Zu viel zu lang aber besser kommuniziert!

Bei den Gettern ist nur der Fehlerfall die Besonderheit: Hier muss nur global klargestellt sein was in einem Fehlerfall (Nicht existenter key) passieren soll. Rückgabe ja|nein oder das werfen einer Ausnahme (Exception).

Lösung

set() sagt nur aus das etwas gemacht werden soll.
Das _wie_ wird nicht geklärt und deshalb stolpern viele Entwickler immer wieder über Nachteile wie: Jetzt muss ich doch in den Code rein und lesen.

Klarheit

  • replace()
  • register()

Mit replace() treffen wir die klare Entscheidung einen Wert zu ersetzen und mit register() sollten wir sicher stellen bestehende Eigenschaften nicht zu überschreiben da wir eine neue Eigenschaft registrieren wollen.

Damit wird auch die Implementierung beider (Bzw. der nun drei) Methoden vereinfacht.
Wer häufig set() verwendet hat bekommt nun einen klaren Zustand zum Inhalt der Methoden der sich in vielen Fällen sogar generalisieren lässt und somit Teilimplementierungen von Settern und Gettern innerhalb von Klassen einfach auslagern lassen können. Z.b. in eine Abstrakte Klasse die von mehreren Objekten verwendet werden kann.

Für den Getter gibt es nun zwei weitere Entscheidungswege:
get() wirft eine Ausnahme (Exception) wenn der angefragte Schlüssel (key) nicht gefunden wird oder (in meinen Augen besser, eleganter) wenn ein definierter Wert zurückgegeben wird.
Siehe Beispiel.
Der Vorteil eines „defaults“ liegt darin weiter machen zu können in dem man auf Rückgabe-Werte eingeht.
Das macht man hoffentlich immer. Oder durch die zugrunde liegende Ausnahme (Exception) die im Fehlerfall (false) der darüber liegenden Funktionalität eine entsprechende Exception werfen wird.

Beispiel

Registry pattern in PHP:

class Mumsys_Registry
{
    private static $_registry = array();

    public static function replace( $key, $value )
    {
        self::$_registry[$key] = $value;
    }

    public static function register( $key, $value )
    {
        if (array_key_exists($key, self::$_registry)) {
            $message = sprintf('Registry key "%1$s" exists', $key);
            throw new Mumsys_Registry_Exception($message);
        }

        self::$_registry[$key] = $value;
    }

    public static function set( $key, $value )
    {
        $message = 'Unknown meaning for set(). Use register() or replace() methodes';
        throw new Mumsys_Registry_Exception($message);
    }

    public static function get( $key, $default=null )
    {
        if ( isset(self::$_registry[$key]) ) {
            return self::$_registry[$key];
        }
        // if a "default" is implemented return that value, otherwise throw an exception
        return $default;
        // or:
        $message = sprintf('Registry key "%1$s" not found', $key);
        throw new Mumsys_Registry_Exception($message);
    }

}

Sonderfall dieser Klasse: Die static Deklaration. Dadurch kommt das Singleton Entwurfsmuster zum tragen (Rein in der Bedeutung und Funktionalität bei der Verwendung. Eine art Magie). Überall im Code wird die Klasse als Objekt verfügbar gemacht und man kann darauf zugreifen und get(), replace() und register() Befehle ausführen.

<?php
    $object = new stdClass;
    Mumsys_Registry::register('meinObjekt', $object);
    // In einer anderen Funktion, zur gleichen Laufzeit!,
    // kann man das Objekt sich dann holen:
    $meinObject = Mumsys_Registry::get('meinObjekt');

Nimmt man die static Deklaration überall raus (und erstetzt „self::$“ durch „$this->“) hätte man eine hervorragende Abstraktion für Getter/Setter und sollte die Klasse auch entsprechend Kennzeichenen:
abstract class GetterSetterAbstract {...}

Weiterführende Links