Loosely coupled vegetables

23 Jan Loosely coupled vegetables

Softwareentwickler zu sein macht Spaß. Man kann die eigene Kreativität ausleben, Dinge neu erfinden und ist stets auf der Suche nach Lösungen. Das ist positiv und dafür liebe ich meinen Beruf. Trotzdem: früher war alles besser. Da gab es nicht jede Woche drei neue Frameworks.

Zu meiner Studienzeit Anfang der 90er begab ich mich als einer der ersten im Bekanntenkreis in das Lager der objektorientieren Programmierung. Das waren echte Meilensteine und es tut gut, sich solche Basics immer wieder vor Augen zu führen. Ich wuchs auf mit Begriffen wir Abstraktion, Wiederverwendbarkeit und „Loose Coupling“. Wir verwenden es intensiv in MediaMillion und es bedeutet so viel, dass Programmteile miteinander reden können, ohne genau zu wissen, wer der Partner in dieser Kommunikation ist. Es ist eine Grundvoraussetzung dafür, dass die Logik des Codes mit unterschiedlichen Dingen umgehen kann.

Wie kann man sich das vorstellen? Nehmen wir mal eine Tätigkeit, wie das Blumengießen. Da wissen zumindest die meisten von uns, worum es sich handelt und was dazu notwendig ist. Früher, also ganz früher, da hat man eine Funktion ohne Eingabe-Parameter geschrieben, die hieß gießeBlumen. In etwa so:

Function gießeBlumen

hole die Gießkanne

für jede Blume im Büro tue dies:

wenn die Gießkanne leer ist dann fülle sie mit Wasser

gieße die Blume

End Function

Die Tatsache, dass keine Parameter verwendet wurden lag vielleicht daran, dass die verwendete Programmiersprache noch keine Parameter kannte. Es war aber auch durchaus üblich, viel Logik in so eine Funktion zu packen. Das resultierte manchmal in unglaublich langen Codepassagen, welche nur noch der Autor verstand. Und selbst der konnte sich nach einem halben Jahr nicht mehr daran erinnern, warum er dies und jenes so implementiert hatte. Denn neben den Parametern, die vielleicht noch nicht entdeckt waren, gab es eine weitere Funktion, die den meisten Entwicklern unbekannt war: Kommentare. Dies ist übrigens bis heute so geblieben.

Später gab es dann natürlich Parameter und das sind wirklich nützliche Dinge. Die erste Implementierung kennt nämlich nur eine Gießkanne und einen Raum – das Büro. Zweifellos ließe sich der ausgefeilte Algorithmus des Blumengießens auch auf andere Räume übertragen und wäre damit wiederverwendbar. Die moderne Form sah dann so aus:

Function gießeBlumen(Gießkanne kanne, Raum raum)

hole die kanne

für jede Blume im raum tue dies:

wenn die kanne leer ist dann fülle sie mit Wasser

gieße die Blume

End Function

Damit waren wir schon in der Lage, die Tätigkeit des Blumengießens mit unterschiedlichen Gießkannen und in unterschiedlichen Räumen durchzuführen. Können Sie den Beginn dieser wunderbaren Anwendung für Gärtnereien sehen? Ich kann ihn schon fast riechen, den Duft der frisch gegossenen Erde.

Nun wurde aber die objektorientierte Programmierung hoffähig und eine Funktion gießeBlumen, ob nun mit oder ohne Parameter, war nicht mehr zeitgemäß. Überhaupt durften es nicht mehr Funktionen sein. Wir erstellten Klassen für Gießkannen, Blumen und Räume und diese bekamen entsprechende Methoden. Das sieht dann so aus:

Gießkanne kanne = holeGießkanne

Raum raum = büro

Für jede Blume blume in raum.alleBlumen tue dies:

Wenn kanne.istLeer dann kanne.mitWasserFüllen

kanne.gieße(blume)

An dieser Stelle entbrennt bereits der Streit unter den Entwicklern. Soll man die Methode „gieße“ lieber bei der Gießkanne programmieren und ihr eine Blume übergeben, so dass sie gegossen werden kann? Oder wäre sie nicht besser bei der Blume aufgehoben und bekäme eine Gießkanne übergeben? Und überhaupt, wer gießt denn da? Das kann ja die Gießkanne schlecht alleine tun, da fehlt doch was.

In der Tat fehlt hier etwas. Wenn man Programmcode oder auch andere Tätigkeiten außerhalb der Programmierung so aufdröselt, dann werden zumindest die offensichtlichen Fehler klar. Wer füllt die Gießkanne auf und wer gießt? Führen wir also noch eine Person ein. Diese Person hat die Aufgabe sämtliche Blumen in einem Raum zu gießen:

Class Person

Method gießeBlumen(Raum raum, Gießkanne kanne)

Für jede Blume blume in raum.alleBlumen tue dies:

Wenn kanne.istLeer dann kanne.mitWasserFüllen

gieße(blume, kanne)

End Method

Method gieße(Blume blume, Gießkanne kanne)

Gieße die blume mit der kanne

End Method

End Class

Natürlich sieht der reale Code nicht so aus, aber es ist für einen Nichtprogrammierer wohl besser lesbar, als so etwas:

class Person {

void gießeBlumen(Raum raum, Gießkanne kanne) {

foreach(Blume blume in raum.alleBlumen) {

if (kanne.istLeer)

kanne.mitWasserFüllen();

gieße(blume, kanne);

}

}

void gieße(Blume blume, Gießkanne kanne) {

// Gieße die blume mit der kanne

}

}

Das kommt zwar unserer Welt deutlich näher, eignet sich für einen Blog-Beitrag aber nur bedingt, da ich beim Leser keine C/C#/JAVA-Kenntnisse voraussetzen mag.

Wir haben also Personen, welche eine einzelne Blume oder alle Blumen in einem Raum mit einer Gießkanne gießen können. Das ist toll und sogar wiederverwendbar, denn es können unterschiedliche Gießkannen und Räume sein und sogar unterschiedliche Personen. Das sieht ja schon ziemlich gut aus, oder? Ok, wir könnten noch ein paar Kommentare hinzufügen, aber der Code ist ja praktisch selbsterklärend.

Zu Beginn erwähnte ich den Begriff „Loose Coupling“. Loses Koppeln. Und Abstraktion. Wir haben einen tollen Algorithmus, welcher beschreibt, wie eine Person mit Hilfe einer Gießkanne alle Blumen in einem Raum gießen kann. Unsere Gärtnerei läuft auf Hochtouren und unser Personal ist im ganzen Bundesgebiet tätig und gießt die Blumen unserer Kunden. Und dann kommt plötzlich eine Firma daher und hat einen Garten. Der soll auch gegossen werden. Blöd, dass passt jetzt nicht so ganz zu unserem Raum. Und im Garten stehen nicht nur Blumen, da sind auch Hecken, Sträucher und Bäume, die brauchen auch Wasser.

An dieser Stelle haben wir viele Möglichkeiten. Wir können der Einfachheit halber annehmen, dass ein Garten auch ein Raum ist. Schließlich steht in unserem Code nirgends, dass ein Raum Wände und eine Decke hat. Bei den Hecken, Bäumen und Sträuchern wird es schon schwieriger, denn da haben wir ein Kompatibilitätsproblem. Das sind eindeutig keine Blumen. Da unser Personal aber nur weiß, wie man Blumen mit einer Gießkanne gießt, können wir einen Garten vorerst nicht bedienen. Es wäre aber ein sehr lukrativer Auftrag und würde ein komplett neues Geschäftsfeld auftun, sodass die Programmierer angehalten sind, eine schnelle Lösung zu finden.

An dieser Stelle wird in der Softwareentwicklung häufig mit Ctrl-C und Ctrl-V gearbeitet. Die Zwischenablage des Grauens! Code wird vervielfältigt als zähle lediglich, wer am Ende des Tages mehr Zeilen geschrieben hat. Und das führt dann dazu, dass man etwas Ähnliches sieht, wie das:

Class Person

Method gießeAlles(Raum raum, Gießkanne kanne)

gießeBlumen(raum, kanne)

gießeSträucher(raum, kanne)

gießeBäume(raum, kanne)

gießeHecken(raum, kanne)

End Method

Method gießeBlumen(Raum raum, Gießkanne kanne)

Für jede Blume blume in raum.alleBlumen tue dies:

Wenn kanne.istLeer dann kanne.mitWasserFüllen

gieße(blume, kanne)

End Method

Method gieße(Blume blume, Gießkanne kanne)

Gieße die blume mit der kanne

End Method

Method gießeSträucher(Raum raum, Gießkanne kanne)

Für jeden Strauch strauch in raum.alleSträucher tue dies:

Wenn kanne.istLeer dann kanne.mitWasserFüllen

gießeStrauch(strauch, kanne)

End Method

Method gießeStrauch(Strauch strauch, Gießkanne kanne)

Gieße den strauch mit der kanne

End Method

Method gießeBäume(Raum raum, Gießkanne kanne)

Für jeden Baum baum in raum.alleBäume tue dies:

Wenn kanne.istLeer dann kanne.mitWasserFüllen

gießeBaum(baum, kanne)

End Method

Method gießeBaum(Baum baum, Gießkanne kanne)

Gieße den baum mit der kanne

End Method

Method gießeHecken(Raum raum, Gießkanne kanne)

Für jede Hecke hecke in raum.alleHecken tue dies:

Wenn kanne.istLeer dann kanne.mitWasserFüllen

gießeHecke(hecke, kanne)

End Method

Method gießeHecke(Hecke hecke, Gießkanne kanne)

Gieße die Hecke mit der kanne

End Method

End Class

Natürlich funktioniert das. Aber spätestens mit der Idee, dass man für das Gießen im Garten ja auch einen Schlauch statt einer Gießkanne verwenden könnte, wird diese Form der Implementierung scheitern. Die Anwendung, welche noch vor wenigen Jahren vollumfänglich erschien, muss einem Redesign unterzogen werden. Wir reden zwar auch weiterhin von Blumen, unterstützen aber auch Bäume, Sträucher und Hecken. Die lassen sich prima als Pflanzen beschreiben. Und gießen können wir nicht nur mit Gießkannen, sondern auch mit Schläuchen und sogar Eimern. Letztlich handelt es sich um ein Gerät, mit dem eine bestimmte Menge Wasser in einer bestimmten Zeit zu einer Pflanze transportiert werden kann, also nennen wir es Wasserzufuhr. Wobei es Unterschiede zu berücksichtigen gibt: ein Eimer und eine Gießkanne müssen aufgefüllt werden, ein Schlauch eher nicht. Alle drei haben aber etwas mit einem Wasserhahn zu tun.

Wir abstrahieren:

Abstract Class Pflanze

Abstract Method gießen(Wasserzufuhr wasser)

End Class

 

Eine abstrakte Klasse ist etwas, was es nie als reales Ding geben wird, weil dazu nicht genug Informationen vorhanden sind. Es wird z.B. nie ein „Tier“ geben. Es gibt reale Hunde, Katzen, Vögel, Pferde etc., aber kein Tier. Tier eignet sich aber prima als Sammelbegriff und als Parameter. Gehen sie mal in eine Tierhandlung und sagen sie „ich möchte ein Tier kaufen“. Zweifelsohne wird die Reaktion sein „was für ein Tier“?

Die abstrakte Klasse „Pflanze“ hat nämlich eine abstrakte Methode „gießen“. Abstrakt bedeutet in diesem Zusammenhang, dass alle Pflanzen gegossen werden können (mithilfe einer Wasserzufuhr), es aber keine allgemeingültige Anleitung dafür gibt. Diese spezifischen Anweisungen werden bei den einzelnen Pflanzentypen näher spezifiziert:

Class Blume Inherits Pflanze

Override Method gießen(WasserZufuhr wasser)

Gieße die Blume mit dem wasser

Benetze die Blüten mit wasser

End Method

End Class

 

Class Baum Inherits Pflanze

Override Method gießen(WasserZufuhr wasser)

Tue dies dreimal: // braucht viel Wasser

Gieße den Baum mit dem wasser

End Method

End Class

 

Class Hecke Inherits Pflanze

Override Method gießen(WasserZufuhr wasser)

Gieße die Hecke mit dem wasser

// wenn Blätter dran sind – lol

Besprühe die Blätter der Hecke mit wasser

End Method

End Class

 

Class Strauch Inherits Pflanze

Override Method gießen(WasserZufuhr wasser)

Gieße den Strauch mit dem wasser

End Method

End Class

 

Wir haben nun verschiedene Pflanzentypen (Blumen, Bäume, Sträucher und Hecken). Die abstrakte Basisklasse „Pflanze“ definierte, dass alle Pflanzen über eine Methode „gießen“ verfügen müssen. Deshalb musste diese Methode auch für alle Pflanzentypen implementiert werden, was sich aber als überaus vorteilhaft erwiesen hat, da das Gießen mit dem Wasser für Blumen und Bäume eben doch sehr unterschiedlich ist. Zugegeben, das ist jetzt etwas konstruiert, aber wenn sie bis jetzt folgen konnten, wollten oder mussten, so ist der nächste Schritt die Belohnung dafür:

Class Person

Method gieße(Raum raum, Gießkanne kanne)

Für jede Pflanze pflanze in raum.allePflanzen tue dies:

Wenn kanne.istLeer dann kanne.mitWasserFüllen

gieße(pflanze, kanne)

End Method

End Class

 

Die Methode „gießen“ ist jetzt wieder so einfach wie zuvor. Sie fragt den Raum nach allen dort enthaltenen Pflanzen und gießt diese, statt einzelne Implementierungen für alle Pflanzentypen aufzuweisen. Anhand des tatsächlichen Pflanzentyps wird die korrekte gießen-Methode aufgerufen. Stellen Sie sich nur vor, dass wir auf diese Weise sogar eine gänzlich neue Pflanze ins Spiel bringen könnten, sagen wir Gemüse. Gemüse ist dann auch eine Pflanze und hat seine eigene Implementierung für das Gießen. Und der Raum liefert beim Aufruf „allePflanzen“ neben Blumen, Bäumen, Sträuchern und Hecken eben auch Gemüse zurück. Was dann nicht mehr geändert werden muss, ist die Methode „gieße“ der Person. Wir haben diese Tätigkeit entkoppelt von den Objekten, die gegossen werden müssen. Loose Coupling.

Als Transferleistung überlasse ich es Ihnen, die Gießkanne in dieser Methode durch die abstrakte Wasserzufuhr zu ersetzen, sowie eine geeignete Basisklasse für Raum und Garten zu finden. Außerdem haben Sie natürlich damit Recht, dass Gemüse ja wohl auch eher abstrakt sei und weiter unterteilt werden müsste in Tomaten, Gurken und Sellerie. Und ob man die Kartoffeln dann auch dazu zählen dürfe.

Wenn Sie nun ein wenig verwirrt sind, dann ist das völlig in Ordnung. Uns geht es schließlich jeden Tag so. Was Sie aber mitnehmen können ist die Tatsache, dass MediaMillion (Gott sei Dank) so implementiert ist, dass es erweiterbar ist, sodass wir trotz der Komplexität des Produktes in der Lage sind, schnell auf neue Anforderungen zu reagieren. Denn die sind nicht nur in der Softwarebranche allgegenwärtig, sondern auch in der Verlagswelt.

Möchten Sie mehr zu diesem Thema wissen?

1Kommentar
  • Birgit
    Veröffentlicht um 15:52h, 23 Januar Antworten

    Großartig!! Ich wusste gar nicht, dass Pflanzen zu gießen, so kompliziert ausgedrückt werden kann.

Einen Kommentar schreiben