Daniel Hundt - Erfahrung Technologien im Backend

zurück zur Technologie-Übersicht

OOP Tutorial - Wann setze ich welches Konstrukt ein?

Inhalt

Ich habe im Laufe der Zeit festgestellt, dass es insbesondere für OOP-Anfänger recht schwer ist, die richtige Wahl der Sichtbarkeit (public, protected, private) und Instanziierbarkeit (static/non-static) zu treffen. Daher folgt nun hier EINE mögliche Hilfestellung (aka Tutorial), wie ich diese Problematik handhabe.

Software ist immer im Fluss: Änderungen sind an der Tagesordnung. Daher sollte man sich das Leben als Programmierer selbst so einfach wie möglich gestalten. Die hier vorgestellten Vorgehensweisen erlauben einfache Anpassungen, wenn sie später nötig werden sollten. Ansonsten erleichtern sie die berühmten 90% der alltäglichen Problemfälle und tragen zudem zu sauberem, lesbarem, wartbarem und erweiterbarem Code bei. Schließlich ist nichts schlimmer, als wenn die Einarbeitung in die Codebasis länger dauert, als den Code einfach neu zu schreiben.

Namespaces - Namensräume - Packages

Anfänger machen oft den Fehler und handeln alles in einer großen (>100 Zeilen) Funktion ab. Wer schonmal in "altem Spaghetticode" wühlen durfte wird mir zustimmen, dass die Übersichtlichkeit dadurch merklich leidet.

  • Man versteht die Funktion nicht auf Anhieb, sondern muss erst alle Zeilen durchgehen
  • Um eine bestimmte Stelle zu finden (Bug oder für neues Feature) muss man viele Zeile überfliegen
  • Da der Code nicht ausgelagert wurde, entsteht viel doppelter Code d.h. doppelte Fehlerwahrscheinlichkeit und doppelter Pflegeaufwand

Daher sollte der allererste Schritt immer sein, die Funktionen so klein wie möglich zu halten indem Code-Fragmente ausgelagert werden. Hierbei sollten ähnliche Funktionen in einer Klasse (wird schnell unübersichtlich) oder zu mindest in einem gemeinsamen Namensraum gesammelt werden. Die Benamung ist für das spätere Wiederverwenden/-finden entscheidend.

Static vs non-static

Die Entscheidung, ob static verwendet werden soll, ist in Zeiten guter IDEs sehr einfach.
Muss es von etwas zum jetzigen Zeitpunkt mehrere Ausprägungen geben (d.h. selbe Schnittstelle aber unterschiedliche Implementierung), dann muss auf "static" verzichtet werden. Für den Großteil der Fälle macht man sich und seinen Kollegen das Leben aber mit "static" einfacher! Statt einem monolithischen System entsteht sofort nebenbei eine Art Microservice-Architektur.

Gibt es zu einem späteren Zeitpunkt dann doch unterschiedliche Ausprägungen, entstehen schnell Konstrukte dieser Art:
LoggerFactory::getInstance()->log(String, UserFactory::getInstance()->getCurrentUser(SessionFactory::getInstance()->getCurrentSession()))

  • Der Code wird aufgebläht
  • Der Code wird unübersichtlich
  • Man muss viel Wissen mitbringen, um die Funktionen nutzen zu können (Wo bekomme ich eine/n User/Session her?)

Das ganze lässt sich für 90% der Fälle einfach statisch Kapseln!

class Log {
	public static function log(String s) {
		LoggerFactory::getInstance()->log(s, UserFactory::getInstance()->getCurrentUser(SessionFactory::getInstance()->getCurrentSession()));
	}
}
// ...
Log::log("so gehts auch!");
				
Sollte man von statischen Methoden später doch zu Instanz-Funktionen wechseln, kann man direkt die oben gezeigte Form nutzen. Ist man in Ausnahmefällen auf eine bestimmte Instanz angewiesen (d.h. ohne Factory), kann man diese natürlich immer noch separat ansteuern.
Helper-Klassen machen den Alltag leichter!

Fazit

Den Zugriff in der obersten Ebene so einfach wie möglich gestalten: kurz, prägnant, public static.

Public - Protected - Private

Als kurze Auffrischung:

  • public - Ist von außerhalb der Klasse aufrufbar
  • private - Ist nur von der eigenen Klasse aus aufrufbar
  • protected - Ist von der eigenen und allen abgeleiteten Klassen aus aufrufbar

Wozu aber Sichtbarkeiten?

Generell sollte man so viel möglich nach außen hin verstecken. Schließlich will man nicht von hunderten unwichtigen Funktionen im Autocomplete erschlagen werden.
Die Logger-Klasse von oben könnte z.B. in eine Datei schreiben und dafür entsprechende Funktionen bereit halten. Diese sind allerdings für die Logging-Funktion (von außen gesehen) unwichtig und sollten daher versteckt werden.
Daher: nur die Hauptfunktion(en) einer Klasse als public kennzeichnen.

private oder protected

Ich habe mir im Zusammenhang mit UnitTests angewöhnt, auf private zu verzichten und stattdessen nur noch protected zu verwenden.

  • Die Wahrscheinlichkeit dass ein Code-Fragment nur an einer Stelle verwendet wird ist gering
  • Im schlechtesten Fall (Laut Murphy äußerst wahrscheinlich :) ) entsteht sonst doppelter Code
  • Für UnitTests kann eine erbende Fake-/Mock-Klasse erstellt werden, die die Sichtbarkeit (nur für den Test!) auf public ändert und so dediziert getestet werden kann