Die Flagge des Marasek

Dekostreifen

English

Aktuell Texte Der Comic Impressum Kalender Suche PHP-Klassen Container-Wizard main.s21

Kategorien

Buch
Computer
Computerspiele
Film
Geschichte
Gesellschaft
Idee
Kunst
Natur
Persönlich
Politik
Programmieren
Religion & Philosophie
Weblog
Weltpolitik
Weltsicht
{{login}}

Objektorientierte Programmierung: Semantik und Atomizität

Permalink
Vorheriger: Money and Second LifeNächster: Schockierend...!
Eingeordnet in: Programmieren

Ein Aspekt der objektorientierten Programmierung, der in meinen Augen in vielen Behandlungen des Themas etwas zu kurz kommt, ist Semantik und Atomizität.

Semantik

Anders als ein einfacher Typ hat das Objekt einer Klasse einen weitergehenden semantischen Gehalt. Nehme ich als Beispiel einen RGB-Farbwert im sRGB-Farbraum, 16,7 Millionen Farben. Diesen kann ich darstellen auf verschiedene Art und Weisen:

  • Array: $color = array("red"=>128, "green"=>172, "blue"=>9);
  • String, hexadezimal: $hex = "80AC09"
  • Integer: $int = 8432649
  • String, CSV: $string = "128,172,9"
  • Drei Variablen: $red = 128; $green=127;$blue=9;

Oder ich kann ein sehr einfaches Objekt schreiben:

class Color {
    private $red;
    private $green;
    private $blue;
    function __construct($red, $green, $blue) {
            foreach(func_get_args() as $key => $value) {
                if(!is_int($value)) {
                    throw new InvalidArgumentException("parameter ".($key+1)." is not an integer");
                }
            }
            
            if($red<0 or $red>255) {
                throw new OutOfRangeException("red is out of range");
            }
            if($green<0 or $green>255) {
                throw new OutOfRangeException("green is out of range");
            }
            if($blue<0 or $blue>255) {
                throw new OutOfRangeException("blue is out of range");
            }
        
        $this->red = $red;
        $this->green = $green;
        $this->blue = $blue;
    }
    
    function getRed() {
        return $this->red;
    }
    
    function getGreen() {
        return $this->green;
    }
    function getBlue() {
        return $this->blue;
    }
}

Wenn nun eine Funktion eine Farbe benötigt, kann ich sie ihr selbstverständlich z. B. als Hex-Wert übergeben, ich_benoetige_farbe("80AC09"), oder als Array, ich_benoetige_farbe($array);

Ich weiss aber nicht, ob dieser Array oder der String wirklich das enthält, was ich brauche - ich muss es nachprüfen, überall dort, wo ich mit der Farbe hantiere. Ungleich einfacher ist es, mit dem Objekt zu arbeiten:

$object = new Color(128, 172, 9);
ich_benoetige_farbe($object);

Dies ist vor allem praktisch, da man seit PHP5 gezielt verlangen kann, dass der Parameter einer Funktion ein bestimmtes Objekt sein muss:

//define with type hinting
function need_color(Color $color) {
    echo "You gave me R ".$color->getRed().", G ".$color->getGreen().", B ".$color->getBlue();
}
$color = new Color(128, 172, 9);
need_color($color);
//short form
need_color(new Color(128, 172, 9));
//error
need_color("80AC09");
//exception
$color = new Color(128, 17, "nine");
$color = new Color(-15, 267, 15);

Man profitiert an dieser Stelle vor allem von dem semantischen Gehalt des Objekts: $color enthält nicht nur einfach irgendetwas, das erst herauszufinden und zu prüfen ist, sondern man weiss, dass es eine Farbe ist. Die Bedingungen für eine Farbe - etwa drei Kanäle, 0-255, können zentral erfasst werden. Hinzu kommt die Möglichkeit, der Klasse noch spezielle Methoden zu verpassen (Helligkeit, in Graustufe umwandeln) oder gleich sehr komplex ein zentrales Farbobjekt zu definieren dass mit dem Lab-Modell arbeitet und dessen Wert frei nach CMYK oder RGB konvertierbar ist.
Ein anderes, eingängigeres Beispiel ist eine meiner Klassen, die Zeit repräsentiert - die habe ich heute einer Funktion übergeben, die prüfen soll, ob eine bestimmte IP auf einer Floodsperre sitzt. Davor war $time ein Integer - doch was bedeutet die Zahl, die ich der Funktion übergebe - Sekunden? Minuten? Stunden? Ein Blick in die Funktionsdefinition oder Dokumentation wäre erforderlich, was ich so für immer vergessen kann.

Atomizität

Dies führt mich zu dem zweiten Thema, Atomizität. Bei Einführungen in das objektorientierte Programmieren ist mir aufgefallen, das gerne Objekte wie "Auto" oder "Backofen" mit Methoden wie "Gas geben", "nach links lenken" oder "Klappe öffnen", "auf 200° erhitzen" als Beispiel genommen werden.
Und genauso sahen meine ersten Klassen auch aus: riesige Titanen, die alle Funktionen in sich vereint haben.

Das ist aber ein grundlegender Fehler. Nehmen wir an, ich wollte eine "Küche" programmieren, so könnte ich natürlich zwei Superklassen Herd und Backofen programmieren. Es ist jedoch so, dass der Herd und der Backofen viele gleiche Eigenschaften haben:

  • beide besitzen eine "wärmeabgebende Einheit"
  • beide besitzen einen Intensitätsregulator

Unterschiede sind:

  • Der Herd hat vier ansteuerbare Wärmegeneratoren, der Backofen zwei
  • Auf dem Herd kann pro Generator eine Speise erhitzt werden
  • Der Backofen hat eine Zeitschaltuhr

Es liegt nahe, sowohl den Generator als auch den Regulator seperat zu programmieren und in beide Systeme einzubauen. Wenn ich beide hinreichend abstrakt halte, kann ich den Intensitätsregulator noch im Kühlschrank, der Stereoanlage und dem Dimmer des Wohnzimmers verbauen.

Superklassen

Den atomaren Klassen stehen Superklassen gegenüber, die ein komplexes Objekt repräsentieren. Sie stehen gewissermaßen höher in der Nahrungskette. Anders als ein Objekt "Schalter" - das in vielen verschiedenen Zusammenhängen gebraucht werden kann - ist das Objekt "Backofen" spezieller und begrenzter.
Im Idealfall bedienen sich Superklassen vieler atomarer Elemente und besitzen wenig eigene Funktionalität. Die Superklasse soll nur vereinen, delegieren und entscheiden. Auf diese Weise erspart man es sich, für jedes Projekt die immer gleichen Probleme lösen zu müssen und hat auch nicht eine zusehends unübersichtlich werdende Superklasse am Hals. In Projekten mit mehreren Mitarbeitern kommt hinzu, dass grössere Ansammlungen von Code das Risiko bergen, dass zwei Teilnehmer sich gegenseitig die Arbeit verpfuschen.

Beispiel Wikiparser

Der Wikiparser - der diesen Text hier in schönes HTML umwandelt - war zunächst eine derartige Superklasse. Die interne Dokumentenstruktur wurde mit der Zeit aber zu unübersichtlich. In der derzeitigen Version habe ich die einzelnen Elemente - Absatz, Aufzählungsliste, Blockquote usw. usf. im wesentlichen als Klassen definiert. Der Parser an sich tut im Grunde kaum mehr, als den Text zu zerlegen, an die entsprechenden Klassen deligieren (die ihn korrekt verwalten) und nachher wieder als HTML zusammensetzen. Damit erspare ich mir unzählige Fallunterscheidungen im Parser selbst.

Als Beispiel für Semantik dient der Aufruf von callback-Funktionen an den Parser, die ursprünglich einen Array mit der geparsten "Adresse" erhalten haben. Problematisch war jedoch immer die Frage, ob eine Adresse ein "Label" hat (d. h. als http://www.hadanite-marasek.de oder Hadanite Marasek erscheinen soll); dass erforderte Fallunterscheidungen in den callbacks. Letzlich war die Struktur des Arrays zu unvorhersehbar.
Die Klasse WikiAddress hingegen folgt klaren Regeln und kann z. B. Auskunft über Label ja/nein geben. WikiAddress ist natürlich auch ein Beispiel für Datenkapselung: Anders als bei einem Array ist die Struktur nicht mehr relevant, solange die Methoden gleich bleiben.
Das Objekt ermöglicht sogar noch eine weitere Verbesserung - sollte ein callback eine angeforderte Ressource nicht laden können, weil sie nicht existiert, kann das callback eine entsprechende Methode aufrufen, wodurch der Wikiparser weiss, dass der Aufruf fehlgeschlagen ist.

Schlüsse

Ich habe lange Zeit rein prozedural/funktional programmiert und OOP zunächst als praktische Möglichkeit gesehen, eine Ansammlung von Funktionen Variablenwerte teilen zu lassen, ohne die Ergebnisse ständig hin- und hertauschen oder als globale Variable definieren zu müssen. Später habe ich es dann als Möglichkeit begriffen, quasi ein Werkstück zu haben, das ich nach und nach mit verschiedenen Methoden bearbeite und forme.
Die wahre Offenbarung liegt jedoch gerade in der Modularität. Das verändert den Entwicklungsprozess natürlich völlig, denn zunächst muss ich mich hinsetzen und planen, welche Bestandteile des Projekts "atomar" sind. Dieses "Zerhacken" erinnert mich stark an den Entwurf einer ordentlich normalisierten Datenbank, da ich mich dort auch fragen muss, welche Daten sich nicht weiter sinnvoll zerlegen lassen.

Im Entwurf kann man schnell von Stock auf Stöckchen kommen. Letzlich kommt es auf den Kontext an, wie bei relationalen Datenbanken auch. In einer Tabelle mit 15 Personen (die in diesem Rahmen bleiben wird) ergibt es keinen Sinn, den Wohnort der Person zu normalisieren, bei 500.000 Datensätzen mit dem expliziten Wunsch, nach Städten abzufragen, sieht die Sache anders aus.
Genauso muss ich nicht wie oben erwähnt Farbkanäle einzeln als Objekt darstellen und noch nicht mal die Klasse schreiben, wenn ich nur HTML-Farbcodes verwalten will. Für ein Grafikprogramm sieht es wieder anders aus.

Ansonsten, immer dran denken: die Welt besteht aus lauter winzigen Elementen!

Kommentieren

Bitte beachten: Kommentare sind nicht sofort sichtbar, sondern werden erst nach einer kurzen Prüfung freigegeben, sofern keine rechtliche Beanstandung vorliegt.
Rechtlich bedenkliche Inhalte werden entweder entschärft oder nicht veröffentlicht.

* Titel  
* Nickname  
* Kommentar