Monday, September 24, 2007

Patterns 1 - Gang of Four

Seit ich mich mit Entwurfsmuster, oder eben Patterns wie ich normalerweise sage, beschäftige haben sich einzelne Steine zu einem grösseren Mosaik zusammengefunden. Das Mosaik hat noch viele Lücken, aber ich möche hier alle mir wichtig erscheinenden und von mir verstandenen Patterns zusammenfassen. Dies ist mein aktuelles "Weltbild" des objektorientierten Software-Designs. Noch ein Wort zu meiner Welt. Meine Welt ist die der grösseren datenzentrierten Geschäftsanwendungen, mit einer Rich-Client Benutzeroberfläche.

In der Folge möchte ich in mehreren Beiträge diese Patterns auflisten und zusammenstellen. In einem ersten Beitrag gehts es um die 'Gang of Four - Patterns' welche eine Ansammlung von systemnahen, technisch orientierten Patterns darstellen.

Abstract Factory: Erzeugt eine Familie von Objekte unterschiedlicher Typen ohne dass man bei der Benutzung gegen eine konkrete Familie programmiert. Als Beispiel dient hier eine IDbFactory welche IConnection, ITransaction für den Datenbankzugriff erstellt. IDbFactory kann nun konkrete für SQL-Server oder Oracle implementiert werden ohne dass es für den Benutzer von IDbFactory einen Unterschied ausmacht.

Builder: Abstrahiert den Konstruktionsprozess für komplexe Objekte. Falls erforderlich kann der Konstruktionsprozess für andere Typen wiederverwendet werden. So kann zum Beispiel das abstrakte Erstellen einer Rechnung mit einer Anzahl Rechnungspositionen mit einem abstrakten Builder realisiert werden. Als Varianten für konkrete Builder wäre ein KreditorenrechnungsBuilder und ein DebitorenrechnungsBuilder möglich

Factory Method: Erzeugt ein Objekt wobei die implementierende Subclass über den konkreten Typ des erzeugten Objekts entscheidet. Ist mit dem Abstract Factory-Pattern dadurch verwandt, dass dort eine Familie von Objekten und hier nur ein Objekt erzeugt wird.

Singleton: Stellt sicher, dass eine Klasse nur einmal instanziert ist und global zugegriffen werden kann. Damit kann zum Beispiel eine Infastrukturdienst wie die Protokollierung einfach für alle Programmstellen zugreiffbar gemacht werden. Das Pattern sollte aber sehr bedacht eingesetzt werden, da man sich damit sehr einfache versteckte Abhängigkeiten baut, welche die Flexibiltät und Wartbarkeit von Programmcode stark einschränken.

Adapter: Ermöglicht den Zugriff auf eine Klasse via ein Interface welches die Klasse selbst aber nicht unterstützt. Als Beispiel könnte man damit in einer Windows.Forms-Anwendung Controls über eine Interface IControl ansprechen obwohl die Windows.Forms-Library kein solches Interface unterstützt. Ein ControlAdapter würde dann Control auf IControl adaptieren.

Composite: Ist eine Datenstruktur in Form eines Baumes mit Vater-Kind-Beziehungen. Jeder Knoten im Baum ist wieder ein Composite mit weiteren enthaltenen Composites oder dann ein Leaf (Blatt) ohne weiteren Composites.

Decorater: Damit kann zur Laufzeit ein Objekt dynamisch erweitert werden. Bestehende Methoden können erweitert werden und auf dem Decorater können neue Funktionalitäten für das Objekt realisiert werden. Als Beispiel könnte man damit eine Klasse Strassenverbindung abhängig von der Strassensituation als GesperrteStrasse oder als StrasseMitStau instanzieren was dann das Verhalten der Strassenverbindung zur Laufzeit beeinflusst.

Chain of responsibilty: Ermöglicht dass eine Anforderung oder eine Meldung von einer Anzahl Objekten verarbeitet werden kann, wobei jedes Objekt eine spezifische Aufgabe wahrnimmt und die Meldung an ein nächstes weiterleitet. Als Beispiel kann das Verarbeiten einer Meldung in verschiedenen Schritten angesehen werden: 'Dekomprimieren der Meldung' -> 'Authentizität der Meldung prüfen' -> 'Meldung interpretieren' usw....

Command: Bildet eine Anfrage an ein Objekt ab und erlaubt damit Transport, Protokollierung, Persistierung usw. der Anfrage. Zum Beispiel kann damit ein Command in einer Queue gespeichert und zu einem späteren Zeitpunkt ausgeführt werden.

State: Ermöglicht einem Objekt sein Verhalten abhängig von einem Zustand zu ändern. Die Implementation des Verhalten wird an den State delegiert.

Strategy: Kapselt einen Algorithmus in einer Klasse und ermöglicht damit eine allgemeine Verarbeitung mit spezifischen Strategien zu parametriesieren. So könnte damit die Berechnung einer Strassenverbindung mit der 'KuerzesterWegStrategie' oder mit der 'SchnellsterWegStrategie' parametrisiert werden. Der darunterliegende Berechnungsalgorithmus ist somit unabhängig und kann mit verschiedenen Strategien kombiniert werden.

Template Method: Kapselt Teile eines Algorithmus in Methoden und erlaubt Subklassen diese Teile zu implementieren. Somit realisiert eine Basisklasse das Grundgerüst eines Algorithmus und die Subklassen können einzelne Verarbeitungsschritte verfeinern.

Sunday, September 9, 2007

IO und funktionale Programmierung in C#

Zusammenfassung
Das seiteneffektfreie Programmieren ist in der funktionalen Programmierung ein wichtiger Grundsatz, welcher in der imperativen Programmierung (z.B C#) oft vernachlässigt wird obwohl dadurch höhere Nachvollziehbarkeit, Wiederverwendbarkeit und Testbarkeit erreicht werden kann. Im folgenden wird Anhand eines Beispiels gezeigt wie eine Konsolenausgabe seiteneffektfrei realisiert werden kann.

Einleitung
In der funktionalen Programmierung werden Seiteneffekte und Zustände wie der Teufel das Weihwasser gescheut. Leicht passiert es dass durch Seiteneffekte Methoden und Klassen, bei entsprechend ungeeignetem Design, ein nicht deterministisches Verhalten zeigen, was Zuverlässigkeit, Stabilität und Fehleranalyse von Programmen umgemein behindert. IO-Operationen können als Beispiel für Funktionalitäten dienen welche gezwungenermassen Seiteneffekte aufweisen.
string line = System.Console.ReadLine();


Wartet das Programm im Methodenaufruf ReadLine() auf eine Benutzereingabe, so ist nicht sicher ob der Aufruf jemals einen Wert zurückliefert (z.B falls der Benutzer am Bildschirm eingeschlafen ist und somit keine Eingabe macht). Weiter liefert der Aufruf ReadLine() bei jedem Aufruf unter Umständen einen anderen Wert zurück, obwohl aus Sicht des Programmes nichts geändert hat. Dieses Verhalten ist in der imperativen Programmierung (C#) normal, in der funktionalen Programmierung aber verpönnt da in dem darunterliegenden mathematischen Verständnis der Programmierung kein Platz für Nichtdeterminismus und Zufall ist. Auch die Methode System.Console.WriteLine() macht aus mathematischer Sicht wenig Sinn da sie keinen Rückgabewert hat und den Zustand des Programmes nicht verändert.

Beispiel
Wie könnte man nun die Seiteneffekte für folgendes Beispiel beheben?
static void Main(string[] args)
{
int index = 0;
var numbers = new int[] { 1, 3, 7, 10 };
foreach (var number in numbers)
{
System.Console.WriteLine("Index {0}: {1}", index++, number);
}
System.Console.ReadLine();
}

Lösung

Die Seiteneffekte können zwar nicht ganz eliminiert werden da eine Ausgabe auf die Console nun mal erwünscht ist. Die Idee ist es den Seiteneffekt an eine unkritische Stelle zu verschieben und den Zustand der gewünschten Ausgabe explizit abzubilden (wie dies ein Monad in der funktionalen Programmierung macht).

static void Main(string[] args)
{
System.Console.Write(
new int[]{1,3,7,10}
.Select( (number,index)=> string.Format("Index {0}: {1}{2}", index,number,Environment.NewLine))
.Aggregate( (lines,line) => lines + line));
System.Console.ReadLine();
}

Unter Verwendung von LINQ wird mit Select und string.Format() jedes Listenelement in eine Ausgabenzeile projiziert. Danach werden alle Zeilen mit Aggregate und 'lines + line' in einen einzigen String aggregiert welcher am Schluss auf die Konsole ausgegeben werden kann.

Da unsere Funktion nun die Ausgabe explizit abbildet, können wir Tests schreiben und prüfen ob die Ausgabe korrekt ist:


[TestMethod]
public void TestOutput()
{
string output = new int[]{1,3,7,10}
.Select( (number,index)=> string.Format("Index {0}: {1}{2}", index,number,Environment.NewLine))
.Aggregate( (lines,line) => lines + line);
Assert.AreEqual( "Index 0: 1" + Environment.NewLine +
"Index 1: 3" + Environment.NewLine +
"Index 2: 7" + Environment.NewLine +
"Index 3: 10" + Environment.NewLine , output);
}