Die Kombination von funktionalen und

Die Kombination von funktionalen
und objektorientierten
Sprachelementen mit F# und C#
Bachelorarbeit
zur Erlangung
des akademischen Grades
BACHELOR OF SCIENCE (B.SC.)
im Studiengang Allgemeine Informatik
vorgelegt an der Fachhochschule Köln
Campus Gummersbach
Fakultät für Informatik und Ingenieurwissenschaften
ausgearbeitet von:
Michael Morbach
Erstprüfer/in: Prof. Dr. Erich Ehses (Fachhochschule Köln)
Zweitprüfer/in: Prof. Dr. Victor (Fachhochschule Köln)
Gummersbach, im Juli 2013
I
Kurzfassung
Kurzfassung
Die vorliegende Bachelorarbeit beschäftigt sich vorwiegend mit der Fragestellung inwieweit sich die Paradigmen der funktionalen und der objektorientierten Programmierung in der heutigen Softwareentwicklung im .NET Umfeld kombinieren lassen und wie
sie sich bereits darin äußern. Eine ausführliche Einführung in die funktionale Programmierung soll dazu beitragen, einen Einblick in die Konzepte und die Sichtweise des
funktionalen Entwickelns und Denkens zu erlangen. Diese Einblicke werden durch den
Entwicklungsprozess einer Anwendung bestärkt, der praxisnah darstellt, in welchen
Bereichen sich die beiden Paradigmen ergänzen können.
Abstract
This bachelor thesis deals mainly with the question how far it is practically possible to
combine the paradigms of functional- and object oriented programming in a .NET environment and how they do manifest themselves already. A detailed introduction to
functional programming should help to gain an insight into the functional concepts and
thinking. These insights are strengthened by the development process of an application,
which demonstrates where functional and object oriented programming complement
each other.
II
Inhaltsverzeichnis
Inhaltsverzeichnis
Kurzfassung ...................................................................................................................... I
Abstract............................................................................................................................. I
Inhaltsverzeichnis .......................................................................................................... II
Abbildungsverzeichnis................................................................................................... V
Abkürzungsverzeichnis ................................................................................................ VI
1
Einleitung ............................................................................................................. 1
2
Grundlagen der funktionalen Programmierung .............................................. 2
2.1
Was ist funktionale Programmierung? .................................................................. 2
2.2
2.2.1
2.2.2
2.2.3
2.2.4
Funktionale Programmierung produktiv einsetzen ............................................... 3
Paradigma der funktionalen Programmierung ....................................................... 3
Deklarativer Programmierstil ................................................................................ 3
Verstehen was ein Programm tut ........................................................................... 5
Nebenläufigkeit ..................................................................................................... 6
2.3
2.3.1
2.3.2
2.3.3
Beispiele funktionaler Programmierung................................................................ 7
Das Verarbeiten von Daten mit LINQ ................................................................... 7
Beschreibung einer Benutzeroberfläche mit XAML ............................................. 8
Komposition .......................................................................................................... 9
3
Kernkonzepte von F# ........................................................................................ 11
3.1
3.1.1
3.1.2
3.1.3
Auswertung funktionaler Programme ................................................................. 11
Verwenden von unveränderlichen Datenstrukturen ............................................ 11
Den Zustand eines Programms durch Rekursion verändern ............................... 12
Verwenden von Ausdrücken an Stelle von Anweisungen .................................. 13
3.2
Schreiben von deklarativem Code ....................................................................... 14
3.2.1 Funktionen als Argumente .................................................................................. 14
3.2.2 Funktionen höherer Ordnung .............................................................................. 15
3.3
Typen in der funktionalen Programmierung ....................................................... 16
4
Anwendungskonzept Pixxler ............................................................................ 18
4.1
Serviceorientierte Architektur ............................................................................. 18
4.2
WCF Service Pixxler ........................................................................................... 19
4.3
Pixxler API .......................................................................................................... 19
4.4
Clientanwendung Pixxler .................................................................................... 20
4.5
F# Bibliothek ....................................................................................................... 20
III
Inhaltsverzeichnis
5
Implementierung des Pixxler Services............................................................. 21
5.1
Was ist ein WCF Service ..................................................................................... 21
5.2
Einrichten der Datenbank .................................................................................... 22
5.3
Ansteuerung der Datenbank aus dem Code......................................................... 23
5.3.1 Einsatz des Entity Frameworks zur Abbildung des Datenbankmodells im
Code ..................................................................................................................... 24
5.3.2 Was ist LINQ? ..................................................................................................... 25
5.4
5.4.1
5.4.2
5.4.3
5.4.4
5.4.5
Authentifizierung eines Benutzers ...................................................................... 27
Annotation von Klassen und Methoden .............................................................. 27
Schnittstellendefinition zum Authentifizieren ..................................................... 28
Abfragen von Daten aus der Datenbank mit LINQ ............................................. 29
Das Repository Entwurfsmuster .......................................................................... 29
Validieren eines Benutzers .................................................................................. 30
6
Funktionale Bibliothek mit F# ......................................................................... 33
6.1
Parallelisierungstechniken ................................................................................... 33
6.1.1 Parallelisieren von kleinen Codesegmenten ........................................................ 33
6.2
6.2.1
6.2.2
6.2.3
Implementierung der grafischen Effekte ............................................................. 34
Berechnungen von Farben ................................................................................... 35
Implementieren und Anwenden von Farbfiltern ................................................. 36
Implementieren eines Unschärfeeffekts .............................................................. 39
6.3
Was kann F# einem C# Entwickler bieten? ........................................................ 40
7
Implementierung des Pixxler WPF Clients ..................................................... 41
7.1
Warum WPF? ...................................................................................................... 41
7.2
7.2.1
7.2.2
7.2.3
Architektur der Anwendung ................................................................................ 42
PRISM ................................................................................................................. 42
Das Architekturmuster MVVM ........................................................................... 44
Dependency Injection .......................................................................................... 45
7.3
Struktur der Anwendung (Shell) ......................................................................... 46
7.4
7.4.1
7.4.2
7.4.3
Implementierung der Module .............................................................................. 48
Anmeldung am Service ....................................................................................... 48
Übersicht über die Alben ..................................................................................... 49
Bearbeiten von Bildern ........................................................................................ 52
8
Fazit .................................................................................................................... 53
8.1
Zusammenfassung ............................................................................................... 53
8.2
8.2.1
8.2.2
8.2.3
Wie hat sich die funktionale Programmierung eingebracht ................................ 54
LINQ zur Abfrage von Datenstrukturen.............................................................. 54
WPF & XAML zur Gestaltung von Oberflächen ................................................ 55
F# zur Implementierung von Kernfunktionalitäten ............................................. 56
IV
Inhaltsverzeichnis
8.3
Persönliches Fazit ................................................................................................ 56
9
Literaturverzeichnis .......................................................................................... 58
V
Abbildungsverzeichnis
Abbildungsverzeichnis
Abbildung 1: SOA (Crofton, 2010) ................................................................................ 18
Abbildung 2: WCF - ABC Prinzip ................................................................................ 22
Abbildung 3: ER Modell der Datenbank ........................................................................ 23
Abbildung 4: EF Modell der Datenbank im Code .......................................................... 25
Abbildung 5: Definition des Interfaces für den BenutzerService ................................... 28
Abbildung 6: Das Repository Entwurfsmuster .............................................................. 30
Abbildung 7: Das BenutzerRepository ........................................................................... 30
Abbildung 8: Die DataContracts..................................................................................... 32
Abbildung 9: Modell des PRISM Frameworks ............................................................. 44
Abbildung 10: Modell des MVVM Entwurfsmusters ................................................... 45
Abbildung 11: Struktureller Aufbau des WPF Clients ................................................... 46
Abbildung 12: Login am Service .................................................................................... 48
Abbildung 13: Login Sequenzdiagramm ........................................................................ 49
Abbildung 14: Inhalte eines Albums darstellen.............................................................. 50
Abbildung 15: Ein Bild im Detail aufrufen .................................................................... 51
Abbildung 16: Ein Bild bearbeiten ................................................................................. 52
VI
Abkürzungsverzeichnis
Abkürzungsverzeichnis
API
Application Programming Interface
CLR
Common Language Runtime
DI
Dependency Injection
EF
Entity Framework (5.0)
GUID
Globally Unique Identifier
IoC
Inversion of Control
LINQ
Language Integrated Query
MVVM
Model-View-ViewModel
SOAP
Simple Object Access Protocol
SoC
Separation of Concerns
UI
User Interface
WCF
Windows Communication Foundation
WPF
Windows Presentation Foundation
XAML
Extensible Application Markup Language
1
Einleitung
1
Einleitung
Die letzten Jahrzehnte hat die objektorientierte Programmierung den Markt in der Softwareentwicklung dominiert, indem sie es erreichte, die Komplexität einer Anwendung
mit Struktur zu versehen und Sprachen für Entwickler intuitiv einsetzbar zu machte.
Doch nicht jede Art von Komplexität lässt sich mit Hilfe von Objekten abbilden. Einige
Bereiche, die z.B. Berechnungen, Analysen oder Transformationen betreffen, ziehen
keinen Nutzen aus Objekten. Somit stehen Entwickler in der heutigen Zeit neuen Herausforderungen gegenüber. Es müssen Programme entwickelt werden, die eine große
Menge an Daten schnell und sparsam verarbeiten können und gleichzeitig auf mehrere
Prozessoren zugeschnitten sind.
Aus dem Schatten der wissenschaftlichen Institutionen haben sich die funktionalen und
deklarativen Techniken der Softwareentwicklung in den vergangenen Jahren mehr und
mehr auch in der Wirtschaft bewährt. In C# wurden generics, anonyme Funktionen und
Ausdrücke zur Abfrage von Daten eingebaut. Eine ausgereifte kommerzielle funktionale
Sprache blieb allerdings bisher aus. Mit F# entwickelte Microsoft seit 2002 eine Sprache die sowohl funktional ist, zudem aber über das .NET Objektmodell ebenso Zugriff
auf objektorientierte Bereiche besitzt.
Programmiersprachen in der heutigen Zeit sind multiparadigmatisch. Objektorientierte
und funktionale Programmierung geht mehr und mehr in einander über und bietet somit
eine weitläufige Liste von einsetzbaren Möglichkeiten bei der Entwicklung von Anwendungen. So kann bereits jetzt in C# auf viele funktionale Techniken zurückgegriffen
werden..
Funktionale Programmierung jedoch beschränkt sich nicht nur auf die Art und Weise
wie Code geschrieben wird. Mehr ist es eine andere Sichtweise wie Probleme gelöst
werden können. In dieser Arbeit sollen diese Betrachtungsweise und einige anwendbare
Techniken bei der Entwicklung einer Software, die sowohl das objektorientierte als
auch das funktionale Paradigma nutzt, aufgezeigt werden.
2
Grundlagen der funktionalen Programmierung
2
Grundlagen der funktionalen Programmierung
Funktionale Sprachen sind Ausdrucksstart und erledigen sogar große Aufgaben, obwohl
der dazu geschriebene Code meist minimalistisch ist. Funktionaler Code wird ergebnisorientierter verfasst und die Art und Weise wie Anweisungen ausgeführt werden, kann
besser vor den Entwicklern versteckt werden. Es ist insgesamt unwichtiger worden wie
die Ergebnisse erreicht werden, denn dieser Weg zur Ergebnisfindung muss nur ein einziges Mal spezifiziert werden.
2.1
Was ist funktionale Programmierung?
Es existiert eine ganze Reihe von unterschiedlichen funktionalen Sprachen und es gibt
keine Funktionalitäten die eben jede dieser Sprachen besitzen müsste. Daher ist es
schwierig eine allgemeingültige Definition, die auf die funktionale Programmierung
zutrifft, zu finden. Was die Sprachen miteinander verbindet sind eher allgemeine Aspekte. Dennoch hat jede dieser Sprachen einen eigenen Stil, um Lösungen für Probleme
zu schaffen. Funktionale Programmierung lässt sich daher wohl am besten im Vergleich
zur imperativen Programmierung beschreiben.
„Functional programming is a style of programming that emphasizes the evaluation of
expressions, rather than execution of commands. The expressions in these languages
are formed by using functions to combine basic values“ 1
Der funktionale Ansatz wird aus dem ersten Satz gut ersichtlich. Die „Auswertung von
Ausdrücken“ repräsentiert den funktionalen Aspekt und die „Ausführung von Anweisungen“ den imperativen Aspekt.

Ausführung von Anweisungen – Ein Programm wird als eine Verkettung von Befehlen ausgedrückt. Die Befehle beschreiben wie das Ende des Programms erreicht wird und was für Objekte während dieses Prozesses erstellt und verändert
werden.

Auswertung von Ausdrücken – Ein funktionales Programm entspricht eher einem
Ausdruck der beschreibt wie z.B. die Eigenschaften von einem Objekt aussehen
1
(Hutton, 2002) http://www.cs.nott.ac.uk/~gmh/faq.html
3
Grundlagen der funktionalen Programmierung
sollen nachdem es erstellt wurde. Es werden keine einzelnen Schritte vorgegeben, die besagen wie das Objekt zusammengebaut wird und es besteht keine
Möglichkeit die Erzeugung eines Objektes zu beeinflussen, bevor es nicht erstellt wurde. Sollte also z.B. eine Tasse Kaffee mit Zucker erstellen werden, so
könnte der Kaffee nicht getrunken werden bevor der Zucker enthalten ist, denn
wenn das Objekt erstellt ist enthält es den Zucker bereits.
Der wesentliche Unterschied ist also der, dass bei der funktionalen Programmierung
Code in Form von Ausdrücken anstatt in einer Reihe von Anweisungen geschrieben
wird. Aus diesem Grund wird Code in der funktionalen Programmierung auf eine ganz
andere Art und Weise verfasst und gekapselt.
2.2
Funktionale Programmierung produktiv einsetzen
Entwickler schätzen die funktionale Programmierung oft durch ihre ausdrucksstarke
und lesbare Art. Dies ist jedoch noch lange kein Grund es auch wirklich zu praktizieren.
In diesem Kapitel soll der Nutzen und die Vorteile der funktionalen Programmierung
herausgestellt werden.
2.2.1 Paradigma der funktionalen Programmierung
Funktionales Programmieren ist ein Programmierparadigma. Das bedeutet, dass es
Konzepte definiert, die genutzt werden können um bestimmte Probleme zu lösen. In der
Objektorientierten Programmierung wird über Problemlösungen in Form von Objekten
nachgedacht. Jede Sprache ist im speziellen etwas anders. So bietet C++ die mehrfache
Vererbung und JavaScript die Prototypen. Funktional zu programmieren bedeutet jedoch nicht gleichzeitig auf das jeweils andere Paradigma zu verzichten. So bietet die
Sprache C#, welche eine im Kern objektorientierte Sprache ist, seit der Version 3.0 auch
einige funktionale Möglichkeiten die ausgenutzt werden können. Auf der anderen Seite
ist F# eine funktionale Sprache, die jedoch das vollständige .NET Objektmodell unterstützt. Es kann also in beiden Fällen genau das Paradigma für ein Problem gewählt werden, welches für die Lösung am besten geeignet ist.
2.2.2 Deklarativer Programmierstil
Beim deklarativen Programmierstil wird die Logik eines Programmes ausgedrückt, ohne
auf konkrete Implementierungsdetails näher einzugehen. Diese Beschreibung könnte
4
Grundlagen der funktionalen Programmierung
zwar auch auf die Definition der funktionalen Programmierung passen, jedoch ist die
Idee der deklarativen Programmierung sehr viel weitläufiger und kann mit vielen unterschiedlichen Technologien umgesetzt werden. Da sich diese Arbeit unter anderem auf
die funktionale Programmierung konzentriert, wird im Folgenden demonstriert wie anhand einer funktionalen Sprache deklarativer Code geschrieben werden könnte.
Anwendungen werden implementiert, indem einem Computer die Ziele anhand eines
vorgegebenen Vokabulars, welches dieser interpretieren kann, beschrieben werden. Bei
der imperativen Programmierung besteht dieses Vokabular aus einzelnen Befehlen. Es
besteht allerdings immer die Möglichkeit neue Befehle, wie z.B. „Zeige Kundendetails“, hinzuzufügen. Das gesamte Programm ist jedoch lediglich eine Aneinanderreihung von einzelnen Schritten, die beschreiben wie eine Gesamtaufgabe erledigt werden
soll. Eine Aufgabe könnte zum Beispiel wie folgt lauten:
„Nimm den nächsten Kunden aus der Liste. Wenn der Kunde in Deutschland wohnt,
dann zeige seine Details. Wenn mehrere Kunden in der Liste vorhanden sind gehe zum
Anfang“
Sobald ein Programm wächst, wird die Anzahl an Befehlen in dem Vokabular schnell
größer und es wird immer schwieriger dieses zu verwenden. Der objektorientierte Ansatz erleichtert dies, weil dieser eine Ordnung und Organisation in die anlaufenden Befehle bringt. So könnten alle Befehle, die mit Kunden zu tun haben, in einer Klassenstruktur unterbringen. Das Programm selbst wäre jedoch immer noch eine Sequenz aus
Befehlen.
Funktionale Programmierung bietet einen ganz anderen Ansatz dieses Vokabular zu
erweitern. Anstatt sich darauf zu beschränken nur neue Befehle zu erstellen, wird darauf
gesetzt neue Kontrollstrukturen zu erzeugen – Primitive die beschreiben wie Befehle
miteinander verknüpft werden können. In imperativen Programmen wird eine Sequenz
von Befehlen zusammengestellt, die eine beschränkte Anzahl von eingebauten Kontrollstrukturen wie z.B. Schleifen nutzt. Wenn jedoch ein typisches Programm betrachtet
wird, so lässt sich beobachten, dass sich die meisten Strukturen wiederholen. Einige
dieser Strukturen sind bekannt und werden Entwurfsmuster genannt.
In dem Kundenbeispiel lässt sich ebenfalls ein Muster erkennen. „Führe den ersten
Befehl für jeden Kunden aus bei dem der Zweite Befehl ‚wahr‘ zurückgibt“. Diesen
Ausdruck kann man auch simplifizieren zu „Zeige die Kundendetails für jeden Kunden
5
Grundlagen der funktionalen Programmierung
aus Deutschland“. In diesem Satz ist „Zeige Kundendetails“ das erste Argument und
„aus Deutschland“ das Zweite.
Beide Sätze zur Lösung desselben Problems werden nun gegenübergestellt:

Nimm den nächsten Kunden aus der Liste. Wenn der Kunde in Deutschland
wohnt, dann zeige seine Details. Wenn mehrere Kunden in der Liste vorhanden
sind gehe zum Anfang

Zeige die Kundendetails für jeden Kunden aus Deutschland
Es lässt sich klar erkennen, dass im ersten Satz genau beschreiben wird wie das Ziel
erreicht werden kann, während der zweite Satz beschreibt was genau erreicht werden
soll. Dies ist auch gleichzeitig der essentielle Unterschied zwischen imperativer und
deklarativer Programmierung.
2.2.3 Verstehen was ein Programm tut
Ein gewöhnliches Programm besteht aus allerhand Objekten die einen internen Zustand
haben, der entweder direkt oder durch Methodenaufrufe verändert werden kann. Das
bedeutet, dass es sehr schwierig sein kann zu erkennen welche Eigenschaften sich durch
einen Methodenaufruf verändert haben. Folgendes Beispiel verdeutlicht dies:
Ellipse und Rechteck (C#)
Ellipse el = new Ellipse( new Rectangle( 0, 0, 100, 100 ) );
Rectangle rc = el.BoundingBox;
rc.Inflate( 10, 10 );
return el;
Bei diesem Ausschnitt kann man nicht wissen, welchen Zustand die zurückgegebene
Ellipse haben wird, ohne den Code auszuführen. Besitzt die BoundingBox eine Rückreferenz zur Ellipse und verändert die Methode Inflate(x,y) das Rechteck oder gibt es
ein neues Rechteck zurück? Wäre letzteres der Fall, so hätte die dritte Zeile keinerlei
Effekt.
Bei der funktionalen Programmierung sind die meisten Datenstrukturen nicht veränderlich. Somit kann eine erzeugte Ellipse nach ihrer Erzeugung nicht mehr verändert werden. Das einzige was getan werden könnte, wäre eine neue Ellipse zu erzeugen und mit
anderen Parametern zu initialisieren. Das erleichtert das Lesen von Quelltext erheblich
wie im nächsten Beispiel leicht zu erkennen ist.
6
Grundlagen der funktionalen Programmierung
Ellipse und Rechteck (C#)
Ellipse el = new Ellipse( new Rectangle( 0, 0, 100, 100 ) );
Rectangle rc = el.BoundingBox;
Rectangle rcNew = rc.Inflate( 10, 10 );
return new Ellipse( rcNew );
Beim Schreiben von funktionalen Programmen mit unveränderlichen Typen ist das Einzige, was eine Methode tun kann, ein Rückgabewert liefern. Wie man im Beispiel erkennen kann gibt die Methode Inflate(x,y) ein neues Rechteck zurück. Dies ist keine
neue Idee in der .NET Welt, denn es existieren auch andere Typen die genauso funktionieren, wie z.B. der Typ DateTime oder String.
Die Lesbarkeit des Quellcodes wird durch diese Variante maßgeblich verbessert. Die
Lesbarkeit ist jedoch nicht der einzige Grund für diesen Ansatz. Ein weiterer Grund ist
die Entwicklung von Anwendungen mit Unterstützung für mehrere Kerne.
2.2.4 Nebenläufigkeit
Möchte man in der traditionellen imperativen Programmierung eine nebenläufige Anwendung entwickeln, so stößt man auf folgende Probleme:

Es ist schwer, existierenden sequenziellen Code in parallel ablaufenden Code
umzuwandeln, da eine große Menge an Code explizit so umgeschrieben werden
muss, dass Threads verwendet werden.

Es muss sehr genau geprüft werden, dass beim Zugriff auf Objekte keine Deadlocks auftreten können und dennoch muss genügend Spielraum zur parallelen
Ausführung bestehen bleiben.
Die Funktionale Programmierung hat darauf folgende Antworten:

Bei Verwendung des deklarativen Programmierstils kann Nebenläufigkeit in bestehenden Code eingeführt werden. Durch Ersetzen der Primitive, die bestimmen wie Befehle kombiniert werden, ist dies jederzeit möglich.

Dank der Unveränderlichkeit von Objekten ist es unmöglich, dass race
conditions2 oder deadlocks3 auftreten. Es ist außerdem direkt ersichtlich, welche
Teile des Programms unabhängig laufen.
2
3
Eine Konstellation in der das Ergebnis einer Operation durch andere Einzeloperationen beeinflusst wird
Ein Zustand, bei dem mehrere Prozesse auf die Zustandsänderung eines anderen warten und sich gegenseitig behindern
7
Grundlagen der funktionalen Programmierung
Durch diese beiden Aspekte wird das Design einer Anwendung maßgeblich beeinflusst,
was zu einer Erleichterung beim Schreiben von parallel ausführbarem Code führt wodurch auch der Vorteil von Mehrkernprozessoren ausgenutzt werden kann. Es sollte
jedoch beachtet werden, dass die Parallelisierung von Code nicht allein durch den Einsatz von nicht veränderlichem Code eintritt, sondern lediglich erleichtert wird.
2.3
Beispiele funktionaler Programmierung
Im Folgenden soll anhand einiger kleinerer Beispiele gezeigt werden, dass funktionale
Programmierung nicht nur in der Theorie einsetzbar ist. Es wird außerdem auf die deklarative Programmierung sowie ihre Vorteile anhand von LINQ und XAML eingegangen.
2.3.1 Das Verarbeiten von Daten mit LINQ
LINQ wurde von Microsoft entwickelt, um ein einheitliches Verfahren für den Zugriff
auf Datenstrukturen verschiedener Arten bereitzustellen. Im Folgenden soll anhand eines Codebeispiels gezeigt werden, welche Vorteile sich durch den deklarativen Programmierstil ergeben.
Imperative Datenverarbeitung (C#)
public IEnumerable<string> GetExpensiveProducts()
{
List<string> filteredInfos = new List<string>();
foreach ( Product product in this.Products )
{
if ( product.UnitPrice > 75.0M )
{
filteredInfos.Add( String.Format( "{0} - €{1}",
product.ProductName, product.UnitPrice ) );
}
}
}
Dieses Codebeispiel ist als eine Sequenz von Befehlen im imperativen Stil geschrieben.
Es wird eine neue Liste erzeugt, danach über alle bestehenden Produkte iteriert und anschließend werden die Produkte, die einen bestimmten Preis übersteigen, in eine neue
Liste eingefügt. Beschreiben wir das Problem etwas abstrakter, so wird klar, dass lediglich eine Auflistung von Produkten gefiltert werden soll.
8
Grundlagen der funktionalen Programmierung
Mit C#, ab der Version 3.0, kann dieses Problem auch mit Hilfe von LINQ gelöst werden. Das folgende Codebeispiel beschreibt wesentlich anschaulicher und lesbarer, welches eigentliche Ziel erreicht werden soll.
Deklarative Datenverarbeitung mit LINQ (C#)
public IEnumerable<string> GetExpensiveProducts()
{
return from product in this.Products
where product.UnitPrice > 75.0M
select String.Format( "{0} - €{1}", product.ProductName,
product.UnitPrice );
}
Dieser einzelne zusammenhängende Ausdruck besteht aus elementaren Operatoren wie
z.B. select und where. Diese Operatoren nehmen wiederum andere Ausdrücke als
Argumente, um zu ermitteln was gefiltert werden soll und welches Ergebnis zurückgegeben werden soll. Diese Operatoren geben dem Entwickler also eine Möglichkeit, verschiedene Teile des Codes zu kombinieren, ohne dabei mehr Code schreiben zu müssen.
Ein weiterer interessanter Aspekt ist, dass dieser Ausdruck wesentlich einfacher zu lesen ist, da die konkrete Implementierung der elementaren Operatoren vor dem Entwickler versteckt wird. Es wurde eine Lösung geschaffen, die einerseits einfacher von der
Implementierung, als auch flexibler in der Anwendung ist. In Kapitel 6.1 wird außerdem ersichtlich, dass es so einfacher ist, Code zu parallelisieren.
2.3.2 Beschreibung einer Benutzeroberfläche mit XAML
Die Gestaltung von Benutzeroberflächen hat sich in den vergangenen Jahren gerade
durch den Einsatz von Designern innerhalb den Entwicklungsumgebungen stark verbessert. Code wird immer häufiger von Generatoren automatisiert erzeugt, da dieser, gerade
bei komplexen Oberflächen, für Entwickler kaum noch überschaubar ist.
Die Windows Presentation Foundation (WPF) und ihre enthaltene deklarative Sprache
XAML gehen dabei einen Schritt weiter. Seit der Version 3.0 sind beide ein fester Bestandteil des .NET Frameworks und bringen neben dem Compiler auch einen Laufzeitparser für XAML mit.
XAML ist eine allgemein einsetzbare deklarative Sprache und besonders geeignet um
Benutzeroberflächen zu gestalten. Sie ermöglicht die Trennung zwischen dem Teil, der
die Ansicht der Oberfläche beschreibt und dem Teil, der die konkrete Programmlogik
9
Grundlagen der funktionalen Programmierung
enthält. Genau wie bei XML wird die Beschreibung der Oberfläche in einer Baumstruktur repräsentiert. Dadurch werden eine wesentlich schlankere Form der Oberfläche sowie eine bessere Lesbarkeit gewährleistet. Einen Designer gibt es nach wie vor, jedoch
sind Anpassungen an automatisch generierten Code einfacher möglich.
Erzeugung eines UI im deklarativen Stil (XAML)
<!-- Deklarativ geschriebene Benutzeroberfläche mit XAML -->
<Canvas Background="Black">
<Ellipse x:Name="greenEllipse" Width="75" Height="75"
Canvas.Left="0" Canvas.Top="0" Fill="LightGreen">
</Ellipse>
</Canvas>
Der XAML Code beschreibt die Benutzeroberfläche durch den Einsatz elementarer
Schlüsselworte und das Festlegen von bestimmten Eigenschaften, so dass der gesamte
Code zu einem einzigen Ausdruck wird. Auch hier wird wieder sehr deutlich, dass lediglich festgelegt wird was das Ergebnis sein soll und das wie in einer Blackbox vor den
Entwicklern versteckt wird.
2.3.3 Komposition
Es wurde bereits gezeigt, dass es möglich ist, viele Probleme durch den deklarativen Stil
einfacher zu lösen, wenn zu diesem Zweck zuvor die nötigen Bibliotheken erstellt wurden. Datenstrukturen können mit Hilfe von LINQ einfach abgefragt und Benutzeroberflächen mit XAML leicht verständlich erstellt werden.
Für die spezifischeren Probleme, die einen eigenen Bereich betreffen, gibt es jedoch
meist keine vorgefertigten Bibliotheken. Die Funktionalität, die vor den Entwicklern in
einer Blackbox versteckt wird, entsteht jedoch nicht auf magische Weise, sondern muss
zunächst einmal implementiert und in einer Bibliothek gekapselt werden. Im Folgenden
wird beispielhaft gezeigt wie solch eine Bibliothek entstehen könnte.
Erstellen einer funktionalen Animation (C#)
var green = Anims.Circle(Brushes.OliveDrab, 100.0f.Anim());
var blue = Anims.Circle(Brushes.SteelBlue, 100.0f.Anim());
var animatedPos = Time.Wiggle * 100.0f.Anim();
var greenMove = green.MoveXY(animatedPos, 0.0f.Const());
var blueMove = blue.MoveXY(0.0f.Const(), animatedPos);
var animation = Anims.Compose(greenMove, blueMove);
10
Grundlagen der funktionalen Programmierung
Es ist nicht wichtig das Beispiel im Detail zu verstehen. Im Grunde genommen werden
lediglich 2 Kreise erzeugt, eine Bewegung mit Hilfe der MoveXY Methode erzeugt und
durch die Compose Methode in eine einzelne Animation gekapselt.
Ein wichtiger Aspekt von deklarativen Bibliotheken ist die Tatsache, dass sie kombinierbar eingesetzt werden können. In diesem Beispiel ist eine solche Komposition z.B.
durch das Erstellen einer einzelnen Animation aus mehreren einzelnen Bewegungen
deutlich geworden. Ein anderes Beispiel wäre LINQ, wo komplexe Abfragen aus mehreren einzelnen kleineren Abfragen erstellt werden können. Somit könnten z.B. auch
eigene Primitive erzeugt werden, um damit Animationen zu erstellen.
11
Kernkonzepte von F#
3
Kernkonzepte von F#
Der Fokus in diesem Kapitel liegt darauf, die Kernkonzepte der Sprache F# kennenzulernen und einen Einblick in allgemeine Ideen sowie elementare Techniken der funktionalen Programmierung zu bekommen.
3.1
Auswertung funktionaler Programme
Im vorherigen Kapitel wurde bereits angedeutet, dass funktionale Programme unveränderliche Datenstrukturen nutzen. Man mag sich nun fragen, wie ein Programm überhaupt etwas tun kann, wenn alles unveränderlich ist. Die Antwort ist kurz wie auch einfach. Ein funktionales Programm wird nicht als eine Verkettung von Ausdrücken beschrieben die den Zustand verändern sondern mehr als eine systematische Anwendung
von Regeln.4
3.1.1 Verwenden von unveränderlichen Datenstrukturen
Wird innerhalb eines funktionalen Programmes von Datenstrukturen gesprochen, so
sind im allgemeinen Werttypen oder Klassen, wie sie aus C# bekannt sind, gemeint.
Diese Datenstrukturen sind in F# unveränderlich, sobald sie zum ersten Mal initialisiert
wurden. In C# können einzelne Daten mit Hilfe des Schlüsselwortes readonly ebenfalls als unveränderlich gekennzeichnet werden. Ebenso ist es auch möglich in F# veränderliche Typen zu erzeugen, jedoch ist der Standard, dass Typen nicht veränderlich
sind. Das bedeutet auch, dass Methodenaufrufe nicht den Zustand einer Struktur verändern, sondern stets einen Rückgabewert besitzen der weiter verarbeitet werden muss.
Das kann bei der Initialisierung einer Liste z.B. wie folgt aussehen.
Erstellen einer Liste
var list = ImmutableList.Empty<int>().Add(1).Add(3).Add(5).Add(7);
Dieses Beispiel ist jedoch extrem simpel und sobald darüber nachgedacht wird ein
komplexeres Problem auf dieselbe Weise anzugehen, kann es schwierig werden.
4
Vgl. computation by calculation (Hudak, 2000)
12
Kernkonzepte von F#
3.1.2 Den Zustand eines Programms durch Rekursion verändern
Um aufzuzeigen wie komplexere Probleme gelöst werden können, wird im Folgenden
eine Funktion implementiert, die die Zahlen x bis y aufsummiert, ohne dabei auf lokale
Variablen zurückzugreifen. Im einfachsten Sinne und durch Verwendung lokaler Variablen könnte diese Funktion wie folgt aussehen:
Aufsummieren von Zahlen mit lokaler Variable (C#)
int SumNumbers( int from, int to )
{
int res = 0;
for ( int i = from; i <= to; i++ )
res = res + i;
return res;
}
Es ist in diesem Fall nicht möglich, die lokale Variable durch eine Wertbindung zu ersetzen, da die Schleife bei jedem Durchlauf einen bestimmten Zustand festhalten muss.
Das bedeutet, dass eine fundamentale Veränderung am Code durchgeführt werden muss
und auf eine Technik zur Anwendung kommt, die sich Rekursion5 nennt.
Aufsummieren von Zahlen durch Rekursion (C#)
int SumNumbers(
{
if ( from >
int sumRest
return from
}
int from, int to )
to ) return 0;
= SumNumbers( from + 1, to );
+ sumRest;
Dieses Beispiel ist funktional, da es lediglich mit Wertzuweisungen und Rückgabewerten arbeitet. Die Rekursion wird hier durch einen Selbstaufruf und eine Abbruchbedingung innerhalb der Funktion repräsentiert. Der Zustand der Berechnung wird nun nicht
mehr in einer lokalen Variablen festgehalten, sondern wird durch die Rekursion selbst
ausgedrückt. Den rekursiven Teil einer Berechnung jedes Mal selbst zu schreiben wäre
jedoch aufwändig und auch hier gibt es wiederum einen Weg den schwierigen Teil zu
verstecken und Probleme zu lösen ohne explizit auf Rekursionen zurückzugreifen.
5
Eine Technik, in der eine Funktion sich selbst über den Aufruf von selbst definiert
13
Kernkonzepte von F#
3.1.3 Verwenden von Ausdrücken an Stelle von Anweisungen
Bei der imperativen Programmierung ist ein Ausdruck ein einfaches Stück Code, welches ausgewertet wird und dann ein Ergebnis liefert. So ist z.B. ein Methodenaufruf
bzw. die Verwendung eines booleschen- oder Zahlenoperators ein Ausdruck. Eine Anweisung hingegen ist ein Stück Code welches den Zustand des Programms verändert.
Eine Anweisung wäre somit z.B. der Aufruf einer Methode, die keinen Rückgabewert
besitzt oder die Zuweisung eine Wertzuweisung zu einer Variablen.
Bei funktionalen Sprachen wird der Zustand durch das Ergebnis einer Funktion repräsentiert und weitergegeben. Wird diese Logik weiter verfolgt, so lässt sich sagen, dass
in der funktionalen Sprache alles als Ausdruck bezeichnet werden kann. Betrachtet man
nochmals das Beispiel aus dem vorherigen Abschnitt, in welchem Zahlen aufaddiert
wurden, so lässt sich feststellen, dass es nicht vollkommen funktional ist, da es in einer
Folge von drei Anweisungen geschrieben ist. Mit Hilfe einer kleinen Anpassung lässt
sich dieses Beispiel jedoch auf eine vollständig funktionale Version verbessern.
Aufsummieren von Zahlen durch Rekursion (C#)
int SumNumbers( int from, int to )
{
return ( from > to ) ? 0
: { int sumRest = SumNumbers( from + 1, to );
from + sumRest; }
}
Der Methodenkörper ist jetzt rein funktional, da er nur noch aus einem einzelnen Ausdruck besteht. Dies ist in der Sprache C# nur in relativ seltenen Fällen möglich, da innerhalb von konditionellen Auswertungen keine lokalen Variablen verwendet werden
können. Obwohl dieses Beispiel sehr minimalistisch gehalten ist, gibt es einen Hinweis
darauf, was man in der funktionalen Sprache alles schreiben kann:

Der Methodenkörper ist ein einzelner Ausdruck. In C# bedeutet das auch, dass
der Körper mit dem Schlüsselwort return beginnt.

Da If-Then-Else ebenfalls eine Anweisung ist, muss man auf den konditionellen Operator (?:) zurückgreifen.

Der Ausdruck im else Block enthält eine Variablendeklaration gefolgt von einem Ausdruck. Es wird eine lokale Variable sumRest deklariert welche im restlichen Codeblock verfügbar bleibt. Variablendeklarationen in F# funktionieren
auf dieselbe Art und Weise. Die Deklaration ist zwar kein Ausdruck aber ein
14
Kernkonzepte von F#
syntaktisches Hilfskonstrukt welches an einen Ausdruck angehangen werden
kann.
3.2
Schreiben von deklarativem Code
Bisher wurde lediglich beleuchtet, welche Auswirkungen die deklarative Programmierung hat und in wie weit die Anwendung dieses Programmierstils das Entwickeln erleichtern kann. Die folgenden Kapitel sollen zeigen, wie dies auf der technischen Seite
überhaupt möglich gemacht wurde.
Aus der technischen Sicht deuten zwei Aspekte besonders auf den deklarativen Stil hin.
Der Erste von beiden wurde zuletzt behandelt – nämlich das jedes Sprachkonstrukt ein
Ausdruck ist. Es wird kein genaues Wissen mehr über einzelne Anweisungen benötigt,
da sich ein Programm aus Ausdrücken zusammensetzt die miteinander kombiniert werden. Der Zweite Aspekte beschäftigt sich mit dem Problem, dass Funktionen für unterschiedliche Zwecke eingesetzt werden können.
3.2.1 Funktionen als Argumente
Wird der zweite Aspekt aus Kapitel 3.2 betrachtet, so stellt kommt folgende Fragen
zustanden: „Wie lässt sich der variable Teil einer Methode von dem immer gleichbleibenden Teil separieren?“. Die Antwort auf diese Frage ist relativ einfach: „Der gleichbleibende Teil bildet den Körper der Methode und die Parameter bilden den variablen
Teil, den die Methode ausführen soll“.
Eine Modifikation, an dem Beispiel SumNumbers aus Kapitel 3.1.2, könnte so aussehen,
dass diese die Funktionalität erhält sämtliche Aggregationen durchzuführen, anstatt lediglich Zahlen aufzuaddieren. Folgendes Beispiel demonstriert dies:
Aggregieren von Zahlen durch Rekursion (C#)
public int AggregateNumbers( Func<int, int, int> op, int init, int from,
int to )
{
if ( from > to ) return init;
int sumRest = AggregateNumbers( op, init, from + 1, to );
return op( from, sumRest );
}
Zur ursprünglichen Funktion sind zwei weitere Parameter hinzugekommen – der Initiale
Wert (init) und eine Operation die beschreibt wie die kommenden Zahlenwerte trans-
15
Kernkonzepte von F#
formiert werden sollen. Die Operation wird in Form eines delegates übergeben, welcher spezifiziert, dass er eine Funktion mit zwei Parametern vom Typ int ist und auch
den Typ int als Rückgabewert besitzt.
Die Idee Funktionen als Argumente zu nutzen ist eines der nützlichsten funktionalen
Konzepte. Wird angenommen, dass eine bestehenden Kundenliste sortiert werden soll,
so wäre der klassische objektorientierte Lösungsweg die Sort Funktion einer Liste zu
nutzen und eine eigene Sortierklasse, die das IComparer Interface implementiert, mit zu
übergeben. Diese Klasse würde bestimmen wie zwei Elemente miteinander verglichen
werden können. Ein nicht ganz unerheblicher Aufwand, wenn bedacht wird, dass eine
Klasse für diesen Vorgang geschrieben, diese zum Einsatz instanziiert und abschließend
als Argument übergeben werden müsste. Ganz davon abgesehen, dass die Lesbarkeit
des eigentlichen Zieles nicht deutlich zu erkennen ist.
3.2.2 Funktionen höherer Ordnung
Inzwischen wurde gezeigt, dass Funktionen wie Werte gehandhabt werden können und
sogar als Parameter für andere Funktionen dienen können. Es gibt zwei wichtige Begriffe die oft genutzt werden, wenn über diese Art von Funktionen gesprochen wird:

Funktion erster Klasse (First-class function) ist eine Funktion in Form eines
Wertes. Man kann diese also beispielsweise als Argument für eine andere Funktion weiter nutzen. Als Ergebnis haben diese Funktionen auch einen Typ, der in
C# als delegate ausgedrückt wird. Beim Aufruf der Funktion greift diese auf
ihren Erstellungskontext zurück. 6

Funktion höher Ordnung (Higher-order function) bezieht sich auf eine Funktion, die wiederum eine Funktion als Argument nimmt oder als Rückgabewert besitzt. Das Beispiel aus Kapitel 3.2.1 ist beispielsweise eine Funktion höherer
Ordnung. Diese Art der Parametrisierung wird in funktionalen Sprachen sehr
häufig eingesetzt und hilft dabei Quellcode deklarativer zu gestalten. 7
Mit einem weiteren Beispiel in der Sprache F# sollen diese Aspekte verdeutlicht werden. Zunächst werden aus einer Liste die ungeraden Zahlen herausgefiltert und anschließend das Quadrat der jeweiligen Zahlen gebildet.
6
7
Vgl. (Microsoft, 2012)
Vgl. (Lorenz, 2009)
16
Kernkonzepte von F#
Verarbeiten einer Zahlenliste mit Funktionen höherer Ordnung (F#)
> let numbers = [1..10]
let isOrdd(n) = n % 2 = 1
let square(n) = n * n
;;
val numbers : int list
val isOdd : int -> bool
val square : int -> int
> List.filter isOdd number;;
val it : int list = [1; 3; 5; 7; 9]
> List.map square (List.filter isOdd numbers);;
val it : int list = [1; 9; 25; 49; 81]
Nachdem eine Liste mit den Zahlen von 1 bis 10 erzeugt wurde, werden zwei weitere
Funktionen gestellt. Die Erste Funktion gibt zurück, ob der übergebene Wert (n) durch
zwei teilbar und somit gerade oder ungerade ist. Die zweite Funktion bildet aus dem
übergebenen Wert das Quadrat.
Nach der Definition der Funktionen, wird eine Funktion höherer Ordnung,
List.filter, genutzt, die wiederum als erstes Argument eine Funktion und als zweites
Argument eine Liste einfordert. Wie anhand der folgenden Zeile zu erkennen ist, wird
als Rückgabewert eine Liste mit ungeraden Zahlen geliefert.
Das darauffolgende Beispiel gibt noch eine weitgehendere Idee. Es wird die Funktion
List.map aufgerufen und als erstes Argument die zuvor geschriebene Funktion square
übergeben. Als zweites Argument wird aber nicht etwa eine gewöhnliche vorgefilterte
Liste übergeben, sondern wiederum eine Funktion die als Ergebnis eine Liste als Rückgabewert besitzt, nämlich selbige Funktion aus dem ersten Beispiel.
3.3
Typen in der funktionalen Programmierung
Da funktionale Sprachen jedes Stück Code als Ausdruck werten, ist es eine eher gewagte Aussage zu sagen, dass jeder Ausdruck einen Typ besitzt. Es bedeutet, dass jedes
syntaktisch korrekte Stück Code einen bestimmten Typ hat. Dieser sagt aus was für eine
Art von Ergebnis erwartet wird sobald ein Ausdruck ausgewertet wird und gibt somit
eine wertvolle Information über einen Ausdruck.
Typen können als eine Art grammatikalische Regeln für die Zusammensetzung von
primitiven Ausdrücken gesehen werden. In funktionalen Sprachen haben Funktionen
17
Kernkonzepte von F#
einen Typ, genauso wie die square Funktion aus dem vorherigen Beispiel. Dieser Typ
bestimmt auch gleichzeitig wie eine Funktion verwendet werden kann. Wird sie mit
einem Argument vom Typ int aufgerufen, so lautet der Typ des Rückgabewertes auch
int.
Wichtiger jedoch ist jedoch die Tatsache, dass der Typ auch vorgibt in wie weit eine
Funktion mit einer anderen Funktion genutzt werden kann. So kann die Funktion square nicht mit der Funktion List.filter verwendet werden, da die Filterung eine Funk-
tion mit dem Typ boolean erwartet. Genau das beschreibt auch eine grammatikalische
Regel – die Typen stellen sicher, dass Funktionen stets in einem sinnvollen Kontext
verwendet werden.
18
Anwendungskonzept Pixxler
4
Anwendungskonzept Pixxler
Um die Kombination von funktionalen und objektorientierten Sprachelementen möglichst praxisnah zu erproben, wurde im Rahmen dieser Bachelorarbeit ein Konzept für
eine Anwendung erstellt, welche für den Rest dieser Arbeit den Arbeitstitel „Pixxler“
trägt. Die Idee dahinter ist eine multiuserfähige Anwendung die Bilder durch verschiedene Farb- und Bildfilter nachbearbeitet und in der Cloud speichert. Die Bilder werden
dabei in zuvor, vom Benutzer, angelegten Alben gespeichert. Da die zustande kommenden Daten jedoch nicht lokal beim Benutzer gespeichert werden sollen, sondern in einer
zentralen Datenbank, korrespondiert die Anwendung mit einem Service innerhalb eines
Netzwerks. Wichtig bei der Konzeption des Projektes war auch der Gedanke der Erweiterbarkeit. Eine absolute Erweiterbarkeit kann allerdings nicht garantiert werden, denn
diese ist für einen Entwickler immer mit zusätzlichem Aufwand und erhöhter Komplexität des Programms verbunden. Bei der Abwägung einen Service als Schnittstelle für
den Datenverkehr und andere Dienstleistungen einzusetzen überwogen die Vorteile jedoch dem Aufwand, besonders in Hinsicht der Multiuserfähigkeit und Erweiterbarkeit
der gesamten Plattform. Weiterhin soll auch die Praxistauglichkeit sowie der moderne
Ansatz der serviceorientierten Architektur in Hinsicht auf funktionale Eigenschaften
näher untersucht werden.
4.1
Serviceorientierte Architektur
Die Idee, die hinter diesem Anwendungskonzept
steckt, nennt sich serviceorientierte Architektur
(SOA). Erreicht wird dadurch, dass ein oder gar
mehrere unabhängige verteilte Services einen
Bestandteil eines Programmes übernehmen. Eine pragmatische Standardisierung von Schnittstellen sorgt, in Verbindung mit XML als ein-
Abbildung 1: SOA (Crofton, 2010)
heitlichem Datenformat, für die notwendige
Interoperabilität. Dadurch wird außerdem ge-
währleistet, dass unterschiedliche Endanwendungen mit Hilfe dieser externen Schnittstellen auf gleiche Weise funktionieren.
19
Anwendungskonzept Pixxler
4.2
WCF Service Pixxler
Das Speichern von allen gesammelten Daten in der Cloud ist ein zentraler Aspekt in
diesem Projekt. Alle, von Clients erzeugten Daten, sollen in einer zentralen Datenbank
gespeichert werden. Um die Datenbank selbst jedoch nach außen hin abzusichern und
eine komfortable und neutrale Schnittstelle für Clientanwendungen zu gewährleisten
wird ein Service mit Hilfe der Windows Communication Foundation (WCF) entwickelt.
Die auf Nachrichten basierte Kommunikation zwischen den Klienten und dem Service
wird dabei über das Simple Object Access Protokoll (SOAP) realisiert. Diese Nachrichten werden verschlüsselt über einen Datenstrom versendet und sind somit gegenüber
Dritten weitestgehend geschützt. Um gespeicherte Daten aus der Datenbank abzufragen
wird auf einen Objektrelationalen Mapper zurückgegriffen. Dieser Mapper erleichtert
die Ansteuerung der Datenbank aus dem Quellcode des Services und ermöglicht den
Einsatz der funktional orientierten .NET Erweiterung LINQ to Entities. Durch den Einsatz von LINQ soll außerdem gezeigt werden, welche Vorteile der funktionale Ansatz
auch im Bereich der Abfrage von Daten aus einer Datenbank haben kann. Der Service
dient somit insgesamt sowohl als Vermittlungsstelle zwischen Anwendungen und Datenbank, als auch als Dienstleister für andere Aufgaben wie die Authentifizierung von
Benutzern.
4.3
Pixxler API
Eine API soll den Zugriff auf den Service als weitere Schicht abstrahieren damit die
Ansteuerung dessen nicht direkt im Quellcode der jeweiligen Endanwendung implementiert werden muss. Diese Bibliothek enthält alle vom Service angebotenen Schnittstellenmethoden, eine Struktur zur lokalen Datenhaltung sowie eine lokale Implementierung des Authentifizierungsmechanismus. Die Verwendung einer solchen Bibliothek
hat den Vorteil, dass die Ansteuerung des Services auch von anderen Endanwendungen
auf gleiche Weise wiederverwendet werden kann und nicht im Quellcode mehrerer
Endanwendungen erneut implementiert werden muss. Es kann im späteren Verlauf sogar in Betracht gezogen werden, diese API öffentlich zugänglich zu machen und Drittanbietern von Software einen Zugriff auf den Service zu gewähren. Die API wird als
eigenständiges Modul in die Endanwendung eingebunden.
20
Anwendungskonzept Pixxler
4.4
Clientanwendung Pixxler
Da auch die Vorteile der deklarativen Programmierung näher vorgestellt werden sollen,
wird die Benutzeroberfläche der Clientanwendung mit der, XML ähnlichen, deklarativen Sprache XAML gestaltet. Sie ist fester Bestandteil der Windows Presentation
Foundation (WPF) und wird eingesetzt um grafische Elemente, Benutzeroberflächen,
Verhaltensweisen, Animationen, Transformationen, Darstellung von Farbverläufen und
weitere mediale Inhalte zu definieren.
Die möglichst hohe Flexibilität der Anwendung wird durch eine modulare Architektur
gewährleistet. Die Modularisierung der Endanwendung wird dabei durch den Einsatz
der .NET Bibliothek PRISM, sowie den modernen Ansatz der Dependency Injection
möglich gemacht, um auch in der Zukunft eine leichte Erweiterbarkeit möglich zu machen. Sogenannte Softwaremodule bilden dabei die eigentliche Funktionalität der Endanwendung. Folgende Module sind für die Anwendung vorgesehen:

Modul für den Login

Modul zur Navigation der Bilderalben und deren Inhaltsanzeige

Modul zur Anzeige von Bildern und deren Bearbeitung
Bei der Entwicklung der Clientanwendung wird C# als Sprache für die Anwendungsarchitektur sowie alle anderen objektorientierten Bereiche verwendet.
4.5
F# Bibliothek
Die Anforderung ist, dass der mit F# implementierte funktional orientierte Teil als Bibliothek in der Clientanwendung bzw. im Service ansprechbar sein sollte. In dieser Bibliothek sollen sämtliche Funktionalitäten bereitgestellt werden, um Bilder mit verschiedenen Farb- und Effektfiltern neu berechnen zu lassen. Da der F# Code beim Kompilieren, genau wie C# Code, in den Zwischencode für die CLR8 umgewandelt wird, ist eine
Verwendung dieser Bibliothek aus einer .NET Anwendung bzw. dem Service problemlos möglich. Voraussetzung dafür ist allerdings die Einbindung der allgemeinen F# Bibliothek aus dem .NET Framework in das jeweilige Projekt.
8
Dies ist die Laufzeitumgebung von .NET
21
Implementierung des Pixxler Services
5
Implementierung des Pixxler Services
Der Pixxler Service stellt eine zentrale Anlaufstelle für ausgelagerte Aufgaben aller Art
dar. Er ist ein elementarer Baustein für sämtliche Endanwendungen und muss somit
auch als erstes konzipiert und implementiert werden. Folgende Aufgaben soll der Service in der ersten Version übernehmen:

Authentifizierung der Benutzer

Vermittlungsstelle für Daten aus der Datenbank
Da es sich um eine multiuserfähige Anwendung handeln soll, wird eine Authentifizierung am Service notwendig. Die Abfrage benutzerbezogener Daten muss dabei innerhalb einer Sitzung ablaufen um sicher zu stellen, dass jedem Benutzer die passenden
Daten zur Verfügung gestellt werden können. Nach der erfolgreichen Anmeldung soll
dem Benutzer eine SessionID 9 vergeben werden, mit welcher dieser dann weitere Leistungen des Services nutzen kann. Die SessionID wird in Form einer GUID repräsentiert
um die weltweite Einmaligkeit der Sitzung zu gewährleisten.
Bei den Leistungen des Services handelt es sich sowohl um die Abfrage von Bildern
und Alben die einem Benutzer zugeordnet sind als auch um Routinen, die zum Speichern neuer Bilder und Alben dienen.
Um die Ansteuerung des Services nicht für jedes unterschiedliche Endgerät neu zu entwickeln und ggf. sogar später eine öffentliche Ansteuerung zu ermöglichen, wird für
den Service eine API in Form einer .NET Bibliothek entwickelt um die Datenstrukturen,
Konnektivität und das Handhaben der Sitzung für die Anwendungsentwicklung bereitzustellen.
5.1
Was ist ein WCF Service
Die Windows Communication Foundation (WCF) ist eine von Microsoft entwickelte
Technologie, die es Entwicklern ermöglicht Kommunikationsplattformen zu erstellen
die mit standardisierten Protokollen (TCP, HTTP) arbeiten. Die WCF ist somit besonders dazu geeignet um serviceorientierte Anwendungen zu entwickeln.
9
Eine eindeutige alphanumerische Zeichenfolge die dazu dient den Klienten wiederzuerkennen
22
Implementierung des Pixxler Services
Ein WCF Dienst ist dabei in folgende Bereiche unterteilt:

Eine Adresse die den Ort der Erreichbarkeit in Form einer URI beschreibt

Die Bindung, welche die Form der Kommunikation sowie eingesetzte Kommunikationsprotokoll beschreibt. Verschlüsselung fällt ebenfalls in diesen Bereich.

Ein Vertrag, der beschreibt welche Dienstleistungen in Form von Methoden der
Service anbietet.
Abbildung 2: WCF - ABC Prinzip 10
Dieses ABC (Address, Binding, Contract) Prinzip abstrahiert somit das Konzept des
Endpunktes.
5.2
Einrichten der Datenbank
Die Daten der Benutzer müssen in einer Datenbank persistent gespeichert werden. Als
Speicherort für diese Daten dient ein Microsoft SQL Server 2008 R2 mit einer Standarddatenbank. Die zu speichernden Daten sind zu Anfang sehr überschaubar und werden auf folgende Strukturen projiziert:

Benutzer

Alben und ihre Zugehörigkeit zum Benutzer

Zugriffsrechte auf Alben anderer Benutzer

Bilder und ihre Zugehörigkeit zum Album
Da die Anwendung darauf ausgelegt ist von mehreren Benutzer genutzt zu werden,
muss die Tabellenstruktur in der Datenbank ebenfalls benutzerbezogen gestaltet werden.
Jeder Benutzer soll X Alben anlegen können und in diesen alle gewünschten Bilder anordnen können. Es soll außerdem berücksichtigt werden, dass in einer späteren Version
10
(Beaulieu, 2010)
23
Implementierung des Pixxler Services
Alben mit anderen Benutzern geteilt werden können. Die Anforderungen lassen sich
wie im Folgenden Tabellenschema auf die Datenbank abbilden:
Abbildung 3: ER Modell der Datenbank
Da ein Benutzer X Alben besitzen soll und diese mit bestimmten Rechten auch für andere Benutzer ausgestattet werden sollen ist hier eine Zwischentabelle notwendig. Der
Besitzer eines Albums wird stets durch einen Rückverweis aus der Albumtabelle ersichtlich. Ein Bild selbst soll nur einem einzigen Album zugeordnet werden.
Auffällig ist, dass die Datensatzschlüssel jeweils als uniqueidentifier gespeichert werden. Dieser entspricht keinem gewöhnlichen inkrementellem Primärschlüssel in Zahlenform, sondern einer GUID. Der Grund für den Einsatz eines solchen Schlüssels liegt
darin begründet, dass die Erzeugung eines eindeutigen Schlüssels schon vor dem Eintragen in die Datenbank auf Programmebene gewährleistet werden kann und keine
Rückfragen zur Datenbank mehr notwendig sind um zu erfahren welche ID der neue
Datensatz erhalten hat.
5.3
Ansteuerung der Datenbank aus dem Code
Um Daten aus einer Datenbank auszulesen gibt es in .NET mehrere Möglichkeiten. Der
älteste Weg dafür ist das Hinterlegen von SQL Befehlen im Quellcode und das Ausführen dieser gegen die Datenbank. Die nötigen Klassen für diese Variante gibt es im .NET
Framework schon seit der ersten Version. Wird davon ausgegangen, immer das best-
24
Implementierung des Pixxler Services
mögliche SQL Statement zu schreiben so ist diese Methode auch zugleich die leistungsfähigste Lösung die möglich ist. Es spricht jedoch einiges gegen diesen Ansatz.
Zum einen wäre da die Fehleranfälligkeit. Ein SQL Statement in Form eines einfachen
Strings kann immer Fehler enthalten, die zugleich auch noch schwierig zu finden sein
können, da ggf. keine Informationen beim Debuggen vorhanden sind wenn eine Ausnahme auftritt. Im schlimmsten Fall fällt der Fehler gar nicht auf, weil das Statement
ausgeführt werden kann, jedoch das falsche Ergebnis zurückgeliefert wird.
Ein weiterer Punkt ist die Strukturierung des Codes und die Möglichkeit der Wiederverwendung. Wohin soll eine solche Fülle an Strings ausgelagert werden und wie lassen
sie sich möglichst einfach in anderen Teilen des Codes wieder- oder weiterverwenden?
Um diese beiden Störquellen im Voraus auszuschließen und einen funktionalen Ansatz
verfolgen zu können, wird in diesem Projekt auf einen objektrelationalen Mapper11 in
Kombination mit LINQ zurückgegriffen.
5.3.1 Einsatz des Entity Frameworks zur Abbildung des
Datenbankmodells im Code
Um bei der Entwicklung auf die LINQ Technologie zurückgreifen zu können und den
Zugriff auf die Datenbank zu erleichtern, wird das Entity Framework in der Version 5.0
eingesetzt. Das Entity Framework ist ein objektrelationaler Mapper von Microsoft mit
dessen Hilfe sich das Datenbankschema direkt aus der Datenbank auslesen lässt und
somit ein Abbild von diesem Modell im Quellcode erzeugt werden kann. Nicht nur erspart sich der Entwickler dadurch die manuelle Erzeugung von Datenobjekten sondern
hat zusätzlich auch den Vorteil die Erweiterung LINQ to Entities zur Abfrage von Daten aus der Datenbank einsetzen zu können. Für dieses Projekt wurde mit Hilfe des EF
nun folgendes Modell erzeugt.
11
Eine Abbildung der Datenbankstruktur im Quellcode
25
Implementierung des Pixxler Services
Abbildung 4: EF Modell der Datenbank im Code
Die Abfrage von Daten mittels gewöhnlicher T-SQL Statements ergibt in der Regel ein
nicht typisiertes Objekt oder ein Array mit undefinierten Daten. Es liegt am Entwickler
diese abgefragten Daten in Datenobjekte umzuwandeln und die einzelnen Eigenschaften
zuzuordnen. Ein Vorgang der nicht nur umständlich anzupassen, sondern auch fehleranfällig ist. Wird bei diesem Vorgang mit Indizes gearbeitet und das Tabellenschema ändert sich auf Seiten der Datenbank so können abgefragte Informationen direkt zu Ausnahmen in der Anwendung oder zur falschen Weiterverarbeitung führen.
Der Code des EF Modells wird bei der Aktualisierung automatisch durch T5 Skripte
erzeugt. Das macht den Einsatz nicht nur weniger anfällig gegenüber Fehlern sondern
auch einfacher in der Wartung des Modells. Jegliche Anpassungen werden nämlich über
die automatische Codegenerierung direkt in die jeweiligen Objekten übertragen.
5.3.2 Was ist LINQ?
LINQ ist eine Spracherweiterung des .NET Frameworks, welche die Möglichkeit bereitstellt Abfragen auf Datenstrukturen durchzuführen. LINQ erweitert C# zu diesem
Zweck um einige Ausdrücke, die dazu führen, dass Daten aus Arrays, Listen aber auch
aus XML Dokumenten oder gar relationalen Datenbanken abgefragt werden können.
26
Implementierung des Pixxler Services
Diese Ausdrücke sind vergleichbar mit den Ausdrücken, wie sie bereits aus SQL bekannt sind und beschreiben immer genau was als Ergebnis geliefert werden soll. Dieser
Aspekt ist auch genau der deklarative Gedanke, der in der Erweiterung steckt.
LINQ Ausdrücke werden unmittelbar in den Quellcode eingebettet und kompiliert. Das
hat den Vorteil, dass Fehler in Abfragen zur Kompilierzeit ersichtlich werden und nicht
erst zur Laufzeit auffallen, wie es bei herkömmlichen SQL Abfragestrings der Fall ist.
Desweiteren präsentieren sich die Ergebnisse einer LINQ Abfrage stets als streng typisierte Ergebnismenge. Ergebnisse aus einer Benutzertabelle werden somit in Form von
Benutzerobjekten, mit den passenden Eigenschaften, zurückgeliefert. Dadurch wird eine
Typsicherheit bereits zur Übersetzung garantiert.
Neben XAML und den Parallel Extensions ist LINQ somit eine weitere deklarative Erweiterung die Microsoft ins Feld schickt. Ihnen allen ist gemeinsam, dass sie nicht im
Detail beschreiben, wie eine Aufgabe, sondern was überhaupt zu lösen ist. In folgendem
Beispiel wird das besonders deutlich: Statt anzugeben, wie die einzelnen Elemente zu
sortieren sind, gibt man lediglich mit der Erweiterungsmethode OrderBy an, dass und
nach welchem Kriterium sortiert werden soll.
Beispielabfrage mit LINQ auf den Speicher
var processes =
from p in Process.GetProcesses()
orderby p.ProcessName
select p;
Kurz und knackig und für die meisten Entwickler wahrscheinlich sehr gut zu lesen.
Aber schon nachdem der Compiler sein Werk verrichtet hat, ist von der Query
Comprehension nichts mehr übrig: Der Compiler übersetzt diese Abfrage nämlich in
äquivalente Aufrufe der Extension Methods aus dem Namepsace System.Linq. Das
sieht dann etwa so aus:
Beispielabfrage mit Lambda Expressions
var processes =
Process.GetProcesses().OrderBy( p => p.ProcessName ).Select( p => p );
Diese Compilermagie ist dem C#- sowie dem VB.NET-Compiler spendiert worden. Die
CLR hat also von LINQ keine Ahnung und musste dazu nicht verändert werden. Die
27
Implementierung des Pixxler Services
eigentliche Funktionalität von LINQ steckt folglich in den Extension Methods. Diese
haben nämlich die Aufgabe, den jeweiligen Teil der Query auszuführen. So wird die
OrderBy-Anweisung einer Query in einen Aufruf der OrderBy-Methode übersetzt. Je
nach Erweiterung wird in der Übersetzungsmethode eine andere Funktionalität bereitgestellt. Bei der eingesetzten LINQ to Entities Erweiterung werden die vollständigen
Statements in T-SQL Befehle übersetzt und an die Datenbank geschickt. Beim Einsatz
dieser deklarativen Erweiterung muss der Entwickler jedoch gar nicht darüber nachdenken wie seine Statements letzten Endes umgesetzt werden, denn das bleibt in einer
Blackbox verborgen. An der Stelle wird Komfort also definitiv höher bewertet als das
letzte Quäntchen Performance zu erzielen – was im Regelfall allerdings eher hilfreich
als störend sein dürfte.
5.4
Authentifizierung eines Benutzers
Um einen Benutzer am Service zu authentifizieren muss dieser eine Kombination aus
Benutzername und Kennwort zum Service senden. Ein Benutzername kann dabei entweder der angemeldete Name oder die E-Mailadresse des Benutzers sein. Benutzername
sowie E-Mailadresse sind somit jeweils eindeutig, d.h. dass sie in der Datenbank als
UniqueKeys angelegt werden. Bei der Registrierung neuer Benutzer wird somit verhindert, dass mehrmals derselbe Benutzername oder dieselbe E-Mailadresse genutzt werden kann. Das eingegebene Kennwort soll jedoch nicht im Klartext in der Datenbank
gespeichert werden. Aus diesem Grund wird es zuvor durch einen Verschlüsselungsalgorithmus in die passende Form gebracht. Der private Schlüssel bleibt dabei nur dem
Service bekannt und der öffentliche Schlüssel ist frei zugänglich. Die verschlüsselte
Übertragung der Daten wird dem Kommunikationsprotokoll des Services überlassen.
Durch
Nachrichtensicherheit
werden
Nachrichten
mithilfe
der
WS-
Sicherheitsspezifikation gesichert. Die WS-Sicherheitsspezifikation enthält Erweiterungen des SOAP-Messaging, durch die Vertraulichkeit, Integrität und Authentifizierung
auf SOAP-Nachrichtenebene sichergestellt werden.
5.4.1 Annotation von Klassen und Methoden
Die Verwendung von Attributen ist ein weiterer Bereich in modernen Sprachen, bei
dem der deklarative Ansatz zur Geltung kommt. Attribute geben Entwicklern die Möglichkeit den Einsatz oder das Verhalten von Klassen, Methoden oder Eigenschaften für
bestimmte Szenarien zu beschreiben. In der Fachsprache nennt man dies auch Annotati-
28
Implementierung des Pixxler Services
on. So können Elemente z.B. für eine Designeroberfläche in einer Entwicklungsumgebung deklariert werden. Es handelt sich deshalb um einen deklarativer Ansatz, da lediglich beschrieben wird was von einem Element erwartet wird und nicht jedes Mal explizit programmiert werden muss wie es umgesetzt wird. Im Folgenden Abschnitt wird
gezeigt wie dieser Vorteil besonders bei einem WCF Service zum Einsatz kommt.
5.4.2 Schnittstellendefinition zum Authentifizieren
Ein WCF Service veröffentlicht sogenannte Serviceverträge nach außen um zu beschreiben in wie weit man mit ihm kommunizieren kann. Es wird ein Interface erstellt
und mit dem Attribut
[ServiceContract]
versehen. Die einzelnen Methoden, die nach
außen verfügbar gemacht werden sollen müssen mit dem Attribut
[OperationContract]
annotiert werden.
Abbildung 5: Definition des Interfaces für den BenutzerService
In diesem Fall wird zunächst lediglich eine Methode zur Authentifizierung benötigt.
Falls die Authentifizierung erfolgt, wird dem aufrufenden Client eine gültige GUID
zurückgegeben, welche gleichzeitig der ID seiner Session entspricht. Mit Hilfe dieser
SessionID ist es dem Client dann möglich weitere Methoden am Service aufzurufen.
Diese SessionID hilft dem Service auch zur erkennen welcher Benutzer welche Methode aufrufen möchte und ob dieser überhaupt dazu berechtigt ist. Um zu verhindern, dass
ein Benutzer auf ewig angemeldet bleibt, soll die Session Zeit nach jedem Aufruf erneut
29
Implementierung des Pixxler Services
auf eine vorkonfigurierte Anzahl an Minuten gesetzt werden. Nach dem Ablauf dieser
Zeit jedoch wird ein erneuter Login Vorgang notwendig. Dieses System der Authentifizierung kann nicht nur für den zu entwickelnden WPF Client verwendet werden, sondern auch für eine spätere Anmeldung auf einer Webseite oder von einem Smartphone.
5.4.3 Abfragen von Daten aus der Datenbank mit LINQ
Um einen Benutzer ordnungsgemäß zu authentifizieren muss ein Abgleich seiner übermittelten Daten mit denen in der Datenbank befindlichen durchgeführt werden. Sobald
es jedoch daran geht auf Daten in einer Datenbank zuzugreifen sollte man sich Gedanken über folgende gegebenenfalls auftretende Schwierigkeiten machen:

Doppelte Abfragen bzw. doppelter Code

Ein hohes Potential an kritischen Fehlern

Keine Typisierung von Daten

Zentralisierung von Daten

Keine Möglichkeit Codestellen einfach und schnell zu testen
Einiger dieser Dinge wurden bereits zuvor im Kapitel 5.3 angesprochen. Durch den Einsatz des Entity Frameworks wurde bereits die Typisierung sowie die Zentralisierung
von Daten erreicht. Um auch die restlichen aufkommenden Fehlerquellen möglichst
gering zu halten, wird auf ein Entwurfsmuster zurückgegriffen welches als Repository
Entwurfsmuster bekannt ist.
5.4.4 Das Repository Entwurfsmuster
Das Repository Entwurfsmuster dient als Vermittlungsschicht zwischen Datenzugriffsschicht, in diesem Fall ist das EF unsere Datenzugriffsschicht, und der Domänenschicht
in welcher Programmabläufe gesteuert werden.
„Repository mediates between the domain and data mapping layers using a collectionlike interface for accessing domain objects. “12
Mit Hilfe dieses Musters wird also zwischen verschiedenen Schichten vermittelt. Daten
werden somit zentralisiert aus einer Datenquelle abgefragt und in Form von Objektentitäten zurückgegeben. Änderungen an Objekten können ebenfalls wieder zurück an das
Repository gesendet werden, so dass diese Änderungen persistent in der Datenquelle
12
(Fowler, Patterns of Enterprise Application Architecture, 2003)
30
Implementierung des Pixxler Services
festgehalten werden. Die Abstrahierung dieser Daten und Domänenschicht hat folgende
Vorteile:

Datenlogik und Abfragen werden zentralisiert verwaltet

Es existiert eine konkreter Zugriffspunkt um Unittests zu entwickeln

Es ist ein flexibles Design, welches sogar bei der Änderung der Datenquelle
noch angewandt werden kann
Die Abfragen können auf zwei verschiedene Arten im Repository bereitgestellt werden.
Einerseits können statische Abfragen bereitgestellt werden die immer bestimmte Ergebnisse liefern. Andererseits können auch Methoden bereitgestellt werden, die einem Entwickler durch Argumente noch die nötige Flexibilität ermöglichen die Abfragen zu beeinflussen. Im Folgenden Diagramm wird die Interaktion zwischen Anwendung, Muster
und Datenquell deutlich.
Abbildung 6: Das Repository Entwurfsmuster 13
5.4.5 Validieren eines Benutzers
Abbildung 7: Das BenutzerRepository
Mit Hilfe der Klasse BenutzerRepository stehen nun einige Methoden bereit um den
jeweiligen Benutzer, der sich am Service anmelden möchte, zu authentifizieren. Das
13
(Team M. )
31
Implementierung des Pixxler Services
entscheidende Kriterium ob ein Benutzer authentifiziert wird, ist ob die Abfrage in diesem Repository einen Eintrag findet oder nicht. Zu diesem Zweck wird überprüft ob das
übergebene Alias mit einem Benutzernamen oder einer E-Mailadresse übereinstimmt
und darauffolgend ob das Kennwort passt.
Abfrage des Benutzers mit LINQ to Entities (C#)
public PixxlerUserDTO GetBenutzerByUsernameOrEmail( string userAlias,
string encryptedPassword )
{
var alias = userAlias.Trim().ToLower();
using ( var pixxlerContext = new DataModel.PixxlerDataContext() )
{
return ( from benutzer in pixxlerContext.pxl_Benutzer
where ( benutzer.Benutzername.ToLower() == alias
|| benutzer.EmailAdresse.ToLower() == alias )
&& benutzer.Passwort == encryptedPassword
select new PixxlerUserDTO()
{
BenutzerID = benutzer.IDBenutzer,
Benutzername = benutzer.Benutzername,
Emailadresse = benutzer.EmailAdresse
} ).FirstOrDefault();
}
}
Was dieser Ausdruck zurückgeben soll entspricht genau den zuvor beschriebenen Anforderungen und sollte für jeden Entwickler auch ohne Kenntnisse von LINQ erkennbar
sein. Würde eine solche Abfrage auf herkömmliche Weise implementiert werden, so
käme eine unleserliche aufgeblähte Methode in der zunächst Daten abgefragt werden,
diese daraufhin in Objekte umgewandelt und anschließend ausgewertet werden müssten
zustande. In dieser Abfrage jedoch, kommt ein konkretes Ergebnis in Form eines typisierten Objektes zurück oder den Wert null falls kein Eintrag in der Datenbank gefunden wird.
Der Rückgabewert der Methode ist ein sogenanntes Datentransferobjekt. Diese Objekte
werden grade im Zusammenhang mit Services sehr häufig genutzt um den Datentransfer
möglichst gering zu halten und genau zu beschreiben welche Daten übertragen werden
sollen. Datentransferobjekte werden genau wie
[ServiceContract]
buten annotiert. Ein Objekt wird mit dem Attribut
[DataContract]
mit Hilfe von Attriannotiert um zu be-
schreiben, dass es sich um ein Datentransferobjekt handelt. Welche Informationen von
dieser Klasse, in Form von Eigenschaften, der Service übertragen soll, wird durch das
32
Implementierung des Pixxler Services
Attribut
[DataMember]
deklariert. Folgende Datentransferobjekte sind im Zuge dieses
Projektes entstanden:
Abbildung 8: Die DataContracts
33
Funktionale Bibliothek mit F#
6
Funktionale Bibliothek mit F#
Es hat sich bisher schon in einigen zuvor angesprochenen Themen erwiesen, dass die
funktionale Programmierung gewisse Vorteile haben kann. Einer der Gründe warum sie
jedoch besonders in den letzten Jahren wichtiger geworden ist, ist die Tatsache, dass die
Parallelisierung von Prozessen sehr viel an Bedeutung gewonnen hat. Das Schreiben
von Code, der auf mehrere Kerne zugeschnitten ist, ist im funktionalen Stil sehr viel
einfacher als im imperativen Stil.
6.1
Parallelisierungstechniken
Das wohl am meisten parallelisierte Konstrukt in der Programmierung ist die ForSchleife. Sobald Schleifendurchläufe unabhängig voneinander durchlaufen werden können, ist es sinnvoll diese in unterschiedlichen Prozessen unterzubringen. Unabhängig
heißt ist diesem Zusammenhang, dass kein Schleifendurchlauf sich auf Ergebnisse oder
Aufrufe eines anderen Schleifendurchlaufs stützt.
6.1.1 Parallelisieren von kleinen Codesegmenten
In dem Folgenden Beispiel wird ein gegebenes Array von Elementen unscharf gemacht,
indem die Elemente wild durcheinander vertauscht werden. Obwohl jeder Durchlauf auf
das Array zugreift ist keiner der Durchläufe von einem anderen Abhängig.
For Schleife mit Berechnung in C#
Und F#
for (int i=1; i<inp.Length-1; i++)
{
var sum = inp[i-1] + inp[i] +
inp[i+1];
res[i] = sum / 3;
}
for i in 1 .. inp.Length - 2 do
let sum = inp.[i-1] + inp.[i] +
inp.[i+1]
res.[i] <- sum/3
Seit dem .NET Framework 4.0 gibt es eine Bibliothek, die das Entwickeln von parallel
laufenden Prozessen vereinfacht. Diese „Parallel Extensions“ sind jedoch leider nicht in
früheren Ausgaben des .NET Frameworks verfügbar.
34
Funktionale Bibliothek mit F#
Um den linken imperativen Teil nun zu parallelisieren, wird die Methode
Parallel.For genutzt, die im System.Threading.Tasks Namensraum
14
gefunden
werden kann. Als Argumente werden, wie bei einer gewöhnlichen Schleife, die Laufzeit
und zusätzlich eine Action<T>, in diesem Fall in Form einer Lambda Funktion, übergeben. Der Typ T gibt den Typ des Arguments an, der an die Funktion übergeben wird. In
F# wäre diese Variante etwas überladen. Es wurde somit vorher eine einfache Funktion
definiert, die den Einsatz der Methode etwas vereinfacht.
Hilfsfunktion (F#)
let pfor nfrom nto f = Parallel.For(nfrom, nto+1, Action<_>(f)) |>ignore
Diese einzelne Zeile verpackt denselben Aufruf wie er in C# erstellt wurde in eine
Funktion. Es wird jedoch die Information ignoriert, ob die Schelife erfolgreich durchgelaufen ist oder nicht.
Parallelisierte Schleife C#
Und F#
Parallel.For(1,inp.Length-1, i =>
{
var sum = inp[i-1] + inp[i] +
inp[i+1];
res[i] = sum / 3;
});
pfor 1 (inp.Length-2) (fun i ->
let sum = inp.[i-1] + inp.[i] +
inp.[i+1]
res.[i] <- sum / 3
)
Deutlich zu erkennen ist, dass diese Methode nahezu genauso einfach zu nutzen ist, wie
das von der Sprache bereitgestellte for Sprachkonstrukt. Für Arrays oder anderen imperative Datenstrukturen ist es besonders sinnvoll auf diese Parallelisierung zurückzugreifen. Später wird diese Technik auch beim Schreiben der Farb- und Effektfilter genutzt.
6.2
Implementierung der grafischen Effekte
Eines der einfachsten Beispiele für große Arrays von Daten ist die Repräsentation eines
Bildes als ein zweidimensionales Array aus Farben. Die WPF Clientanwendung wurde
so konzipiert, dass ein Bild vom Benutzer ausgewählt werden kann um dann einen gewünschten Filter darüberzulegen. Im Folgenden Abschnitt wird gezeigt wie die Filter
entwickelt werden und wie man diesen Effekt auf einzelne Bereiche des Bildes anwenden kann.
14
Namensräume dienen in .NET der thematischen Gruppierung und Einordnung von Typen
35
Funktionale Bibliothek mit F#
6.2.1 Berechnungen von Farben
Um grafische Effekte, wie z.B. Unschärfe oder Graustufen, als Filter zu implementieren, müssen verschiedene Berechnungen mit Farben durchgeführt werden. Zu diesem
Zwecken kann der Color Typ im Namensraum System.Drawing genutzt werden. Die
Farben Rot, Grün und Blau müssen jedoch separat gehandhabt werden, was nicht immer
ganz komfortabel ist.
Es gibt jedoch auch einen etwas einfacheren Weg diese Berechnungen in C# oder F#
durchzuführen. In diesem Projekt wurde die Operatorenüberladung genutzt um einen
eigenen Farbtyp zu implementieren. Wenn später die Unschärfe auf ein Bild angewendet wird, gibt es die Möglichkeit einfach Farben zu addieren und die herauskommende
Farbe durch die Anzahl der Pixel zu dividieren.
Der Typ SimpleColor in F#
[<Struct>]
type SimpleColor(r:int, g:int, b:int) =
member x.R = r
member x.G = g
member x.B = b
// Farbe mit Farbwerten 0-255 erstellen
member x.ClipColor() =
let check c = min 255 (max 0 c)
SimpleColor(check r, check g, check b)
// 2 Farben addieren
static member (+) (c1:SimpleColor, c2:SimpleColor) =
SimpleColor(c1.R + c2.R, c1.G + c2.G, c1.B + c2.B)
// Farbwerte durch eine Ganzzahl dividieren
static member DivideByInt (c1:SimpleColor, n) =
SimpleColor(c1.R / n, c1.G / n, c1.B / n)
// Farbwerte mit einer Ganzzahl multiplizieren
static member (*) (c1:SimpleColor, n) =
SimpleColor(c1.R * n, c1.G * n, c1.B * n)
// Farbe mit nullen initialisieren
static member Zero = SimpleColor(0, 0, 0)
Der Typ ist mit dem .NET Attribut Struct annotiert. Diese spezielle Attribut meldet
dem F# Compiler, den Typ als Wertetyp zu kompilieren. Es ist in diesem Fall wichtig
einen Wertetyp zu nutzen, da große Arrays mit diesem Wertetyp erstellt werden und
richtige Objekte auf dem Heap extrem ineffizient wären.
36
Funktionale Bibliothek mit F#
Genau wie in C# werden überladene Operatoren durch statische Eigenschaften des jeweiligen Typs implementiert. Eine Ausnahme bildet in diesem Fall der Operator zum
dividieren. Es ist jedoch eher typisch, dass beim Dividieren der Dividend und der Divisor vom selben Typ sind. Daher wurde diese Methode separiert. Außerdem wird so weiterhin die Möglichkeit geboten zwei SimpleColor durcheinander zu dividieren.
Weiterhin ist zu beobachten, dass der Typ nicht veränderlich ist, so wie es in der funktionalen Programmierung üblich ist. Jede Operation des Typs gibt eine neue Farbe zurück.
6.2.2 Implementieren und Anwenden von Farbfiltern
Zunächst wird einer der einfacheren Effekte näher betrachtet: der Farbfilter. Im späteren
Verlauf wird in diesem Kapitel auch beschrieben wie ein Unschärfeeffekt implementiert
wird. Ein Farbfilter selbst ändert lediglich die Koloration einzelner Pixel eines gesamten
Bildes.
Die Repräsentation eines Bildes in .NET geschieht mit Hilfe der Bitmap Klasse aus
dem System.Drawing Namensraum. Diese Klasse ermöglicht den Zugriff auf einzelne
Pixel durch die Methoden GetPixel() und SetPixel(). Diese zu verwenden wäre jedoch äußerst ineffektiv, da sie etwa dem grafischen Äquivalent entsprechen eine Datei
bei jedem Zugriff zu öffnen. Daher ist es in diesem Fall wesentlich effektiver, das Bild
als zweidimensionales Array direkt im Speicher zu verändern. Die Konvertierung einer
Bitmap in ein zweidimensionales Array erfolgt durch die selbst implementierten Methoden ToArray2D() und ToBitmap().
Die Implementierung der Filter ist in C# und F# relativ ähnlich, jedoch wird die Nutzung der Filter in beiden Sprachen recht unterschiedlich sein, da in F# auf Funktionen
höherer Ordnung15 zurückgegriffen werden kann um 2D Arrays zu manipulieren.
6.2.2.1
Graustufenfilter und Aufhellungsfilter
Um einen Graustufenfilter zu berechnen wird ein oft verwendeter Mittelwert zur Berechnung genutzt, da das menschliche Auge das grüne Licht anders wahrnimmt als das
Rote und das Blaue. Der Aufhellungsfilter ist sogar noch einfacher. Jede der Farben
wird mit dem Faktor zwei multipliziert, was Farben außerhalb der Wahrnehmung des
15
Vgl. Kapitel 3.2.2
37
Funktionale Bibliothek mit F#
Menschen erzeugt. Daher wird im Anschluss an die Berechnung die Methode ClipColor() aufgerufen um das Limit jeder Farbe einzuhalten.
Graustufen und Aufhellungsfilter (F#)
// Grayscale
let Grayscale(clr:SimpleColor) =
let c = (clr.R*11 + clr.G*59 + clr.B*30) / 100
SimpleColor(c, c, c)
// Hellere Farben
let Lighten(clr:SimpleColor) =
(clr*2).ClipColor()
Jetzt da die Filtermethoden implementiert wurden, geht es daran diese auf das jeweilige
2D Array, die Interpretation des eigentlichen Bildes, anzuwenden. Im Gegensatz zu C#
besitzt F# bereits ein Modul Array2D um 2D Arrays, ähnlich wie eindimensionale Arrays, zu verarbeiten. Dieses enthält sogar die map Funktion, was die Implementierung in
F# ganz besonders trivial macht. Zum besseren Verständnis des Ablaufs wird im Folgenden aber auch aufgezeigt, wie die Implementierung in C# aussehen könnte.
Schreiben der Funktion zur Anwendung von Filtern (C#)
public static SimpleColor[,] RunFilter
(this SimpleColor[,] arr, Func<SimpleColor, SimpleColor> f)
{
int height = arr.GetLength(0), width = arr.GetLength(1);
// Ergebnis ist ein neues Array
var result = new SimpleColor[hgt, wid];
// Neue Farbe für jeden Pixel berechnen
for (int y = 0; y < height; y++)
for(int x = 0; x < width; x++)
result[y, x] = f(arr[y, x]);
return res;
}
Im Gegensatz dazu würde folgender F# Aufruf denselben Effekt haben. Beide Ansätze
jedoch sind noch nicht parallelisiert.
Schreiben der Funktion zur Anwendung von Filtern (F#)
// Wende den übergebenen Filter 'f' auf jedes Element im Array an
let runFilter f arr = Array2D.map f arr
38
Funktionale Bibliothek mit F#
6.2.2.2
Parallelisieren der RunFilter() Methode
Zurzeit werden alle diese Aktionen noch in einem einzelnen Prozess ausgeführt. Da die
eingebaute Array2D.map Funktion nicht von Haus aus parallelisiert ist, kommt hier eine
eigene Implementierung zum Einsatz. In C# würde man dazu die notwendige Schleife,
die jedes Element in dem Array ansteuert, über die aus 6.1.1 bekannte Parallel.For
Methode parallelisieren.
Der ursprüngliche Code enthält zwei Schleifen, jedoch wird lediglich die äußere der
beiden parallelisiert, um die Erzeugung unnötig vieler Aufgaben zu vermeiden. Für die
Anwendung des Filters in der C# Variante würde das bedeuten, dass die Äußere der
beiden Schleifen wie folgt geschrieben werden würde:
Parallelisierung des C# Filter
...
// Neue Farbe für jeden Pixel berechnen
Parallel.For(0, height, y => {
for(int x = 0; x < width; x++)
result[y, x] = f(arr[y, x]);
});
...
Es zeigt sich, dass für die Parallelisierung des Codes in diesem Fall lediglich eine Änderung von zwei Zeilen im Code notwendig war. Das muss jedoch nicht immer so einfach
wie in diesem Beispiel sein, da Änderungen an diesem Mechanismus auch immer Probleme nach sich ziehen können. Dies kann z.B. Änderungen an dem aktuellen Zustand
eines Objektes betreffen. In diesem Fall wurde lediglich eine lokale Variable verändert,
welche nach außen nicht zugreifbar ist.
Um die Parallelisierung auch für unser F# Codestück zu ermöglichen, wird die map
Funktion in einem anderen Namensraum erneut geschrieben. Dazu wird auch die bereits
in 6.1.1 erzeugte Funktion pfor wieder eingesetzt um im inneren Parallel.For anzusprechen.
Parallele map Funktion für das 2D Array (F#)
module Array2D =
module Parallel =
// Im vorh. Modul Array2D wird ein weiteres Modul Parallel eingefügt
// Die Funktion f soll auf alle Elemente von ‚arr‘ angewandt werden
let map f (arr:_ [,]) =
let width, height = arr.GetLength(0), arr.GetLength(1)
39
Funktionale Bibliothek mit F#
// Neues Array als Ergebnis erzeugen
let result = Array2D.zeroCreate width height
// Parallelisieren der äußeren Schleife
pfor 0 (width-1) (fun x ->
for y = 0 to height - 1 do
result.[x, y] <- f(arr.[x, y]) )
result
Das eigentliche Ziel war das Erzeugen eines parallel ablaufenden grafischen Filters.
Doch wurde nicht nur dieser Filter erzeugt, sondern es wurde sogar eine generell einsetzbare Funktion geschaffen, die es ermöglicht eine unbestimmte Funktion f auf jedes
Element eines zweidimensionalen Arrays parallel ablaufen zu lassen. Desweiteren ist
der Code generell lesbarer geworden, da der Name der Funktion für Entwickler verständlicher ist und mehr Aufschluss darüber gibt, wie sie eingesetzt werden kann.
6.2.3 Implementieren eines Unschärfeeffekts
Der andere Bildeffekt, der für dieses Projekt entwickelt wird, soll nicht nur ein einfacher Farbfilter sein. Der sogenannte Unschärfeeffekt (Blur) ist in der Bildverarbeitung
auch als Weichzeichnungsfilter bekannt, da er, durch das Vermischen von mehreren
Pixeln, harte Übergänge weicher erscheinen lässt. Der Vorgang des Vermischens basiert
dabei auf der Berechnung eines neuen Pixelwertes anhand von mehreren originalen Pixeln. Für den Filter kann zwar, wie zuvor bei den Farbfiltern, eine Pixelweise Transformation stattfinden, jedoch wird ebenso der Zugriff auf das gesamte Bild benötigt
damit die Berechnung durchgeführt werden kann.
Der Transformationsvorgang selbst ist nicht sehr schwer aber er zeigt sehr gut wie sich
die funktionale von der imperativen Version abhebt.
Unschärfeeffekt Funktion (F#)
let blur(arr:SimpleColor[,], x, y) =
// Überprüfen ob die Indizes passen
let hgt, wid = arr.GetLength(0) - 1, arr.GetLength(1) - 1
let checkW x = max 0 (min wid x)
let checkH y = max 0 (min hgt y)
// Sammeln von naheliegenden Pixeln
seq { for dy in -2 .. 2 do
for dx in -2 .. 2 do
yield arr.[checkH(y + dy), checkW(x + dx)] }
|> Seq.average // Durschnittsfarbe berechnen
40
Funktionale Bibliothek mit F#
Die Parameter der Funktion bestehen aus dem Bild selbst sowie den x,y Koordinaten
der Pixel, die neu berechnet werden sollen. Würde diese Funktion in der imperativen
Variante implementiert werden, so müsste eine veränderbare variable mit 0 initialisiert,
alle nahestehenden Pixel addiert und das Ergebnis durch die Anzahl an Pixeln dividiert
werden.
Mit F# gibt es jedoch einen einfacheren deklarativen Ansatz, der es ermöglicht eine
Durchschnittsfarbe zu errechnen. Zunächst werden erneut zwei Funktionen definiert,
welche die Abgrenzungen sicherstellen sollen. Danach wird ein Sequenzausdruck genutzt, welcher eine Liste mit allen naheliegenden Pixeln zu erzeugt, um dann abschließend mit der List.average Funktion den Durchschnittswert zurückzugeben.
Damit die Listenfunktion mit selbstdefinierten Werttypen, wie dem SimpleColor Typ,
funktionieren kann, muss diese folgende Dinge wissen:

Was ist der 0 Wert für diesen Typ

Wie werden Werte addiert

Wie kann ein Wert diesen Typs durch eine Ganzzahl dividiert werden
Blickt man zurück auf die Implementierung des SimpleColor Typs in Kapitel 6.2.1 so
lässt sich erkennen, dass bereits alle Vorausgesetzen Operationen bereitgestellt werden.
6.3
Was kann F# einem C# Entwickler bieten?
F# ist bestens geeignet um einfache funktionale Konzepte und Bibliotheken zu Beginn
eines Projektes zu erkunden, um diese dann später mehr in einer traditionell orientierten
Weise zu implementieren. Als C# Entwickler entwickelt man Software, die in der Wirklichkeit zur Anwendung kommt. Dennoch könnte man die Vorteile von F# folgende
Arten ausnutzen:

F# kann sehr gut dazu eingesetzt werden um Prototypen von einzelnen Codesegmenten zu erstellen und zu testen. Mit Hilfe der interaktiven F# Konsole
können somit erste Entwürfe erstellt und im späteren Verlauf auch auf C# übertragen werden.

Wie es in diesem Projekt auch praktiziert wurde, kann eine F# Bibliothek aus einem C# Projekt heraus referenziert und eingesetzt werden. F# wird vollständig
in der .NET Sprache kompiliert und somit gibt es keinen Grund C# zu bevorzugen.
41
Implementierung des Pixxler WPF Clients
7
Implementierung des Pixxler WPF Clients
Die für dieses Projekt entwickelte WPF Anwendung ist eine Komposition mehrerer
Entwicklungstechniken und .NET Technologien. Neben der WPF selbst, kommt in diesem Projekt auch die PRISM Bibliothek zum Einsatz. Sie ermöglicht das Aufteilen der
Anwendung in mehrere kleine Module, was sie letztlich zu einer modularen Anwendung macht. Das Grundgerüst der Anwendung wurde bereits im Vorfeld dieses Projektes mit der Technik der Dependency Injection entworfen. In den folgenden Abschnitten
soll näher beleuchtet werden, wie und warum einzelne Techniken zum Einsatz kamen
und wie sie den Prozess der Entwicklung unterstützt haben.
7.1
Warum WPF?
Die Entscheidung für den Einsatz von WPF fiel primär, weil sie eine deklarative Sprache für die Gestaltung von Oberflächen nutzt. Wie praktikabel und nützlich dieser Ansatz sein kann soll ebenfalls im Laufe dieser Arbeit geklärt werden. Weitere Aspekte die
für den Einsatz dieser Technologie sprachen sind:

Breit gefächerte Integration – bevor WPF entwickelt wurde, mussten Entwickler für Windowssysteme sehr viele unterschiedliche Technologien beherrschen
um 3D, 2D, Video, Audio oder Textverarbeitung in ihren Programmen verwenden zu können. Mit WPF sind alle diese Bereiche, durch ein konsistentes Programmiermodel, abgedeckt

Unabhängigkeit von der Bildschirmauflösung – WPF setzt einen Schwerpunkt auf Vektorgrafik und ist somit unabhängig von der Bildschirmauflösung.
Zoomen ist somit innerhalb der Anwendung problemlos möglich.

Hardwarebeschleunigung – WPF greift bei der Berechnung von Grafiken und
Steuerungselementen auf die Direct3D Schnittstelle zurück. Das ermöglicht eine
weichere Navigation und erhöhte Performance beim Bildaufbau. Grade in diesem Projekt ein Gewinn bei der Darstellung vieler Bilder.

Möglichkeit der deklarativen Programmierung – Um den Nutzen der deklarativen Programmierung herauszustellen ist diese neue Technologie ideal. Der
Einsatz der Sprache XAML ist ähnlich dem Erstellen einer Webseite mit Hilfe
42
Implementierung des Pixxler WPF Clients
von HTML, jedoch ist mit XAML sehr viel mehr möglich. So können beispielsweise auch 3D Modelle mit XAML ausgedrückt werden.

Große Vielfalt bei der Komposition von Steuerelementen – Steuerelemente
wie Knöpfe, Comboboxen oder Menüs können in WPF miteinander kombiniert
werden. Mit XAML können beispielsweise verschachtelte Menüs mit animierten
Comboboxen erzeugt werden.
7.2
Architektur der Anwendung
Beim Entwurf der Anwendung wird darauf geachtet, dass wichtige architektonische
Prinzipien bei der Softwareentwicklung eingehalten werden. Die Trennung von Zuständigkeiten und die lose Kopplung von Programmelementen zählen zu den wichtigsten
Prinzipien bei der Softwareentwicklung. Die Bibliothek PRISM fördert diese Entwicklung von flexiblen und leicht Anpassbaren Architekturen für WPF Desktop- oder Windows Phone Anwendungen in dem sie eine Komposition von eigenständig entwickelten
Komponenten ermöglicht.
7.2.1 PRISM
PRISM ist ein Framework, bereitgestellt von der Microsoft Patterns & Practices Group,
dass die Entwicklung von Anwendungen unterstützen soll, die einerseits komplex sind
aber auch leicht zusammen zu stellen sein sollen. Um dies zu erreichen werden Entwurfsmuster und Techniken verwendet um die Anwendung in einzelne modulare Teilstücke zu zerlegen. Diese Zerteilung führt zu einigen Vorteilen die sich nicht nur bei der
Entwicklung sondern auch bei der Planung der Anwendung bemerkbar machen:
Wiederverwendbarkeit – Sämtliche visuelle Komponenten können in anderen Projekten die ebenfalls WPF nutzen wiederverwendet werden. Somit könnte man einige bereits entwickelte Komponenten in einem Windows Phone oder Silverlight Projekt wiederverwenden. Komponenten ohne visuelle Darstellung können sogar in Projekten jeglicher Art wiederverwendet werden.
Erweiterbarkeit – Erweiterungen lassen sich, insbesondere durch die lose Kopplung,
sehr leicht in das bestehende Konstrukt einbetten. Abhängigkeiten unter den einzelnen
Modulen sind nicht vorhanden und können somit nicht zu Konflikten führen.
Flexibilität – Dadurch, dass bei dem Entwurfsmuster der IoC stets gegen Interfaces
entwickelt wird, ist ein Ersetzen oder Erweitern von konkreten Implementierungen sehr
43
Implementierung des Pixxler WPF Clients
flexibel und einfach möglich. Updates einzelner Module können individuell ausgeliefert
werden. Das macht sogar das Veröffentlichen der Anwendung leichter
Team-Zusammenarbeit – Das Entwickeln mehrerer Module zur gleichen Zeit ist möglich. Die Teams stehen sich bei der Entwicklung und Codeverwaltung nicht gegenseitig
im Weg. Eine Versionsverwaltung des Quellcodes wird dadurch ebenfalls erleichtert.
Fehler Toleranz – Die Qualität der Gesamtanwendung kann durch Komponententests
verbessert werden. Das Testen einzelner Komponenten führt auch dazu, dass Testfälle
einfacher konzipiert und durchgeführt werden können.
Wartbarkeit – Die Wartung der Anwendung unterteilt sich auf einzelne Komponenten
die wiederum an die zuständigen Teams weitergegeben werden können.
Modularisierung – Die Modularisierung der Anwendung führt dazu, dass ggf. sogar
Drittanbieter weitere Module entwickeln können.
Natürlich steht den ganzen Vorteilen bei der Entwicklung der Nachteil gegenüber sich
mit den benötigten Entwurfsmustern zu beschäftigen. Um die Bibliothek zu verstehen
und nutzen zur können wurde auf folgende Entwurfsmuster zurückgegriffen:

MVVM

Command

Dependency Injection (DI)

Inversion of Control (IoC)

Separation of Concerns (SoC)
Um sich eine PRISM Anwendung vom Aufbau her besser vorstellen zu können sei gesagt, dass sie sich aus folgenden Teilen zusammensetzt:
Shell – Die Shell ist das visuelle Grundgerüst der Anwendung. Sie bezeichnet das Fenster welches auf der obersten Ebene liegt und gleichzeitig als Container für alle Programmrelevanten Inhalte dient. In der Shell werden Regionen definiert die als Platzhalter für geladene Module fungieren.
Regionen – Die in der Shell hinterlegten Regionen können jederzeit aus den einzelnen
Modulen angesprochen und zur Laufzeit für die Anzeige neuer Ansichten beansprucht
werden.
44
Implementierung des Pixxler WPF Clients
Module – Diese Module machen die eigentliche Funktionalität der Anwendung aus.
Module müssen jedoch voneinander unabhängig sein um die Trennung von Abhängigkeiten einzuhalten.
Ansichten (Views) – Module enthalten ggf. eine oder mehrere Ansichten um einem
Benutzer eine Interaktionsmöglichkeit bereitzustellen.
Bootstrapper – Der Bootstrapper ist der Initialisierungskern der Anwendung. Durch
ihn wird bestimmt, welche Module in die Anwendung geladen werden, welche DI Strategie verwendet wird und was vor dem Erzeugen der Shell noch getan werden soll.
Abbildung 9: Modell des PRISM Frameworks 16
7.2.2 Das Architekturmuster MVVM
Das Architekturmuster Model View ViewModel gilt als eine spezialisierte Version des
Model View Presenter (MVP) Musters von Martin Fowler. MVVM wird bei der Ent-
16
(Team P. a., 2012)
45
Implementierung des Pixxler WPF Clients
wicklung von ereignisgesteuerten Benutzeroberflächen wie z.B. WPF oder HTML5
genutzt.
Das eigentliche Ziel des MVVM Musters ist die klare Trennung von Benutzeroberfläche, Datenstrukturen und ausführender Logik. Dazu teilt das Muster die Oberfläche in
die folgenden drei konzeptionellen Teile auf:
Model – Das Model entspricht etwa den Daten die in der View verarbeitet werden sollen. Es kann sich dabei z.B. um Daten eines Services oder um Geschäftsdaten aus einer
Datenbank handeln.
View – Die View entspricht der Benutzeroberfläche mit welcher der Benutzer interagiert.
ViewModel – Das ViewModel ist die Zwischenschicht zwischen View und Model und
dient als eine Art Kleber. Es bereitet einerseits die Daten aus dem Model so auf, dass sie
an die View gebunden werden können, kontrolliert andererseits aber auch Ereignisse
aus der View um entsprechende Handlungen anzustoßen.
Abbildung 10: Modell des MVVM Entwurfsmusters 17
Wichtig beim Einsatz dieses Entwurfsmusters ist die Tatsache, dass die View etwas
vom ViewModel wissen sollte, jedoch das ViewModel völlig unabhängig von der View
sein muss. Ebenso verhält es sich zwischen ViewModel und Model. Das Model sollte
keinerlei Rückreferenzen zum ViewModel haben, da ansonsten die Trennung der Zuständigkeiten verletzt wird.
7.2.3 Dependency Injection
Die Dependency Injection ist, in der Objektorientierten Programmierung, eine Methode
um die Abhängigkeiten, die ein Objekt besitzt, erst zur Laufzeit bereitzustellen. Die DI
17
(Wikipedia, Wikipedia MVVM Muster, 2012)
46
Implementierung des Pixxler WPF Clients
ist eigentlich eine konkretere Ausprägung der Inversion of Control wie sie Martin Fowler 2004 getauft hat.
„Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various [Inversion of Control] advocates we settled on
the name Dependency Injection.” 18
Um nicht zu tief in die Methodik der DI einzutauchen, kann für den Rest dieser Arbeit
einfach gemerkt werden, dass es eine Technik für die Entkopplung und Zerteilung der
Anwendung ist Da die Abhängigkeiten einzelner Komponenten erst zur Laufzeit bereitgestellt werden, lassen sich einzelne Komponenten auch unabhängig von ihren Abhängigkeiten individuell kompilieren.
7.3
Struktur der Anwendung (Shell)
Da sich die Anwendung aus mehreren unabhängigen Modulen zusammensetzt, ist eine
Voraussetzung für die Funktionalität, dass innerhalb der Oberfläche bestimmte Regionen definiert werden in denen die Module ihre eigenen Inhalte präsentieren können.
Abbildung 11: Struktureller Aufbau des WPF Clients
18
(Fowler, MartinFowler.com, 2004)
47
Implementierung des Pixxler WPF Clients
So wurde die Shell in vier Bereiche aufgeteilt die sich wie folgt ergeben haben:
Informationsleiste (1) - Auf der oberen Seite der Anwendung liegt die Region für Informationsinhalte. Sie dient als Platzhalter für ein oder mehrere Module die Informationen innerhalb einer schmalen Statusleiste darstellen möchten. Derzeit existiert dort eine
Informationsangabe bezüglich der Konnektivität zum Service sowie der aktuellen Uhrzeit.
Inhalt (2) – Im Fokus des Benutzers sollte stets der Inhalt stehen. Daher wurde dieser
Bereich zentral ausgerichtet und der meiste Platz eingeräumt. Das aktuell dort hinterlegte Modul entspricht von der Funktionsweise in etwa der Inhaltsanzeige eines Webbrowsers. In mehreren Tabs können verschiedene Alben zeitgleich geöffnet werden und es
können innerhalb eines Albums auch mehrere Bilder gleichzeitig geöffnet und verändert
werden. Eine Navigationsmöglichkeit von Bild zu Bild ist auch innerhalb eines Tabs
möglich.
Menü (3) – Direkt links neben Inhaltspräsentation befindet sich die Region für das Menümodul. Das aktuelle Modul lässt dort durch eine Baumansicht die verschiedenen Bilderalben des Benutzers darstellen. Eine Verschachtelung von Bilderalben ist derzeit
noch nicht möglich, da die Datenbank noch keine kompatible Struktur dafür abbildet.
Weitere Menüs wie z.B. eine Ansicht für Favoriten könnten im späteren Verlauf hinzugefügt werden. Um mehr Platz für Inhalte zu erlangen, kann die Menüleiste komplett
eingefahren werden.
Suche (4) – Zwischen der Informationsleiste und der Kombination aus Inhalt und Menü
befindet sich der Platzhalter für ein Suchmodul. Dieses Modul befindet sich derzeit
noch in der Konzeption. Geplant ist hier ggf. später nach eigenen Bild- und Alben titeln
oder denen von Freunden suchen zu können.
Die Definition einer Region geschieht im XAML Code der Shell und wird durch das
PRISM Framework weiter interpretiert. Eine Definition sieht z.B. wie folgt aus:
Definition der ContentRegion (XAML)
<ContentControl DockPanel.Dock="Left" VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
prism:RegionManager.RegionName="ContentRegion"
Margin="0,0,20,20" />
48
Implementierung des Pixxler WPF Clients
7.4
Implementierung der Module
Das Grundgerüst für die Anwendung ist nun gegeben. Wie die eigentlichen Module
implementiert werden, in wie weit XAML als Oberflächensprache und F# zur Manipulation der Bildinformationen eingesetzt wurde, wird in den folgenden Kapiteln näher
erläutert.
Wie bereits bei der Konzeption der Anwendung in Kapitel 4.4 definiert wurde, stehen
folgende Module besonders im Fokus:

Modul für den Login

Modul zur Navigation der Bilderalben und deren Inhaltsanzeige

Modul zur Anzeige von Bildern und deren Bearbeitung
7.4.1 Anmeldung am Service
Um auf eigene benutzerbezogene Daten zugreifen zu können, ist eine Anmeldung am
Service nötig. Das erste Modul stellt somit eine Benutzeroberfläche zur Verfügung um
eine solche Verbindung zum Service herstellen zu können.
Abbildung 12: Login am Service
49
Implementierung des Pixxler WPF Clients
Die View ist dabei relativ einfach gestaltet. Zwei Textboxen ermöglichen die Eingabe
von Benutzername und Kennwort und ein Knopf löst ein Ereignis aus, dass die
PixxlerAPI anspricht um die Anmeldung am Service anzustoßen.
Abbildung 13: Login Sequenzdiagramm
Über mehrere Abstraktionsebenen wird in der Datenbank erfragt ob der Benutzer als
solcher in der Datenbank angelegt und somit autorisiert ist weitere Anfragen an den
Service zu stellen. Ist dies der Fall so wird auf Seiten des Services eine Sitzung erstellt
und dem Benutzer eine eindeutige ID für diese zurückgegeben. Wie die einzelnen
Schritte für die Authentifizierung auf der Seite des Services ablaufen, wurde bereits im
Kapitel 5.4 geklärt.
7.4.2 Übersicht über die Alben
Das Albummodul soll dem Benutzer einerseits die Möglichkeit bieten eine Übersicht
über vorhandene Alben zu erlangen, als auch die Bilder in einem Album zu betrachten.
Es muss außerdem gewährleistet sein, dass ein Benutzer neue Alben anlegen sowie weitere Bilder hochladen kann.
Nachdem ein Benutzer also am Service angemeldet ist, werden zunächst alle vorhandenen Alben vom Service erfragt und in einer lokalen Struktur im Speicher gehalten. Um
bei der Darstellung der Inhalte eines Bilderalbums ein wenig auf die Vorteile von
XAML eingehen zu können, wurde die View so gestaltet, dass Bilder in Form von Polaroids in verschiedenen Blickwinkeln dargestellt werden.
50
Implementierung des Pixxler WPF Clients
Abbildung 14: Inhalte eines Albums darstellen
Eine solche Darstellung von Inhalten, wäre mittel gewöhnlichem C# Code ein ungeheurer Aufwand. Was in diesem Fall jedoch dahinter steckt ist zunächst lediglich eine Liste
von vier PixxlerPictureDTO Objekten. Diese ist an ein ListBox Steuerelement gebunden welches im XAML Code definiert wurde.
Definition der ListBox (XAML)
<ListBox Style="{DynamicResource ListBoxStyle}" x:Name="lb"
ItemContainerStyle="{DynamicResource ListBoxItemStyle}"
ItemsSource="{Binding Path=AlbumBilder}"
ItemsPanel="{DynamicResource ItemsPanelTemplate1}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" />
Eine der nützlichsten Eigenschaften von WPF ist die Möglichkeit, dass sich Oberflächenelemente selbst neu gestalten lassen, ohne dabei die eigentliche Funktionalität des
Steuerelementes aufzugeben. Wie anhand des obigen Ausschnitts zu erkennen ist, wurde für jedes Element in der Liste ein Stil ListBoxItemStyle definiert. Diese Vorlage
enthält den Stil für die Darstellung eines jeden Objekts in der gebundenen Liste. Die
Darstellung eines einzelnen Elementes sieht in XAML grob wie folgt aus:
51
Implementierung des Pixxler WPF Clients
Vorlage zur Darstellung eines Bildes (XAML)
....
<Image x:Name="img" Source="{Binding Path=Bilddaten}" Height="200"
Stretch="Uniform" RenderTransformOrigin="0,0">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1" x:Name="scaleTrans"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
<TextBlock Text="{Binding Path=Titel, XPath=Rotation}"
Visibility="Collapsed" />
....
In Kapitel 2.3.3 wurde die Möglichkeit der Komposition von mehreren Elementen aufgezeigt. In diesem Ausschnitt wird diese nochmals sehr gut deutlich. Nicht nur wird
beschrieben wie sich ein einfaches Bild präsentieren soll, sondern wird zugleich, durch
einen geschachtelten Ausdruck, festgelegt wie dieses transformiert werden soll.
Da diese übereinander liegende Betrachtungsweise von Bildern natürlich nur eine allgemeine Übersicht verschafft, wird über einen sogenannten Trigger, der ebenfalls in der
Vorlage eingebettet ist, auch die Möglichkeit gegeben ein Bild in den Vordergrund zu
stellen.
Abbildung 15: Ein Bild im Detail aufrufen
52
Implementierung des Pixxler WPF Clients
7.4.3 Bearbeiten von Bildern
Wird in der Vollansicht eines Bildes auf den Knopf „Bearbeiten“ oben rechts geklickt,
so öffnet sich der Bildeditor. Dieser stellt auch gleichzeitig das zuletzt implementierte
Modul in diesem Projekt dar. Verschiedene Funktionen wie der Farbfilter und der in
Kapitel 6.2.3 entwickelte Unschärfeeffekt können auf der linken Seite genutzt werden
um die vorhandenen Bildinformationen zu verändern.
Abbildung 16: Ein Bild bearbeiten
Die Anwendung der Bibliothek stellt sich als leicht und intuitiv heraus. Ein einfacher
Referenzverweis bietet bereits die Möglichkeit auf sämtliche, in der Bibliothek erstellten, Typen und Klassen zuzugreifen. So steckt hinter dem Aufruf, ein Bild verschwimmen zu lassen, lediglich folgender kleiner Codeausschnitt.
Anwendung der Bildbearbeitungsbibliothek (C#)
Bitmap bildDaten = currentPicture.Bilddaten;
var array2DBilddaten = Tww.Pixxler.BitmapUtils.ToArray2D( bildDaten );
var blurPic = Tww.Pixxler.ImageEffects.ParallelBlur(konvertierteBilddaten);
currentPicture.Bilddaten = Tww.Pixxler.BitmapUtils.ToBitmap(blurPic);
Wie in Kapitel 6.2.2 bereits erklärt, wird hier zunächst die Bitmap in ein Array des
Typs SimpleColor konvertiert um eine leichtere Manipulation durchführen zu können.
Die Anwendung des Unschärfeeffektes ist ein simpler Funktionsaufruf, der zwischenzeitlich auch parallelisiert wurde.
53
Fazit
8
Fazit
Man könnte diese Arbeit mit den Worten schließen „So, das war funktionale Programmierung und einige Einsatzgebiete in einem objektorientierten Umfeld“, doch wäre das
eine sehr wage Aussage. Genau wie bei allen Sprachen und Technologien gibt es keine
klaren Abgrenzungen was möglich ist und wozu sie eingesetzt werden sollen. Mal ganz
davon abgesehen, dass längst nicht alle Konzepte und Fähigkeiten von funktionalen
Sprachen in dieser Arbeit behandelt wurden. Wichtig war, dass die fundamentalen Konzepte und Ideen, die hinter der funktionalen Programmierung stecken, vermittelt wurden
und gezeigt wurde wie man sie anhand vorhandener Technologien, auch in der realen
Softwareentwicklung, wiederfinden und einsetzen kann.
Das folgende Kapitel dient als Zusammenfassung und Rückblick auf die behandelten
Themen.
8.1
Zusammenfassung
Die vorliegende Arbeit beschäftigt sich vorwiegend mit der Fragestellung inwieweit
sich die Paradigmen der funktionalen und der objektorientierten Programmierung in der
heutigen Softwareentwicklung im .NET Umfeld kombinieren lassen und wie sie sich
bereits darin äußern. Eine ausführliche Einführung in die funktionale Programmierung
soll dazu beitragen einen Einblick in die Konzepte und die Sichtweise des funktionalen
Entwickelns und Denkens zu erlangen. Ein wichtiger Aspekt bei dieser Sichtweise ist
auch die Erkenntnis, dass deklarative Programmierung eine zentrale Rolle spielt. Diese
Einblicke werden durch den Entwicklungsprozess einer Anwendung bestärkt, die praxisnah darstellt in welchen Bereichen sich funktionale, deklarative und objektorientierte
Programmierung bereits ausprägt.
Einer dieser Bereiche ist die Abfrage von verschiedenen Datenstrukturen. Gezeigt wird
hier das komplexe Kombinationen von Entwurfsmustern durch ein einfaches funktionales Konzept ersetzt werden können. Das Framework LINQ bietet durch seine deklarative Art eine Lösung an, die im Kern funktional ist, sich jedoch hervorragend in objektorientierte Strukturen einbetten lässt. Im Vordergrund steht hierbei nur noch das was
und nicht mehr das wie. Dies entspricht auch gleichzeitig dem Kernkonzept der deklarativen Programmierung. Genutzt wird LINQ in dieser Arbeit jedoch nicht nur um Daten-
54
Fazit
strukturen abzufragen, die sich im Speicher befinden, sondern auch um Anfragen an
eine SQL Datenbank zu stellen.
Ein weiteres Einsatzgebiet der deklarativen Programmierung ist die Gestaltung von Benutzeroberflächen. Es ist eines dieser Gebiete, bei dem sich die objektorientierte Programmierung schwer tut und deklarative Programmierung seine Stärken herausstellen
kann. Komplexe Benutzeroberflächen äußern sich oft durch Codeklassen mit mehreren
tausend Zeilen, die imperativ beschreiben, wie Elemente erzeugt, angeordnet und welche Eigenschaften ihnen zugeordnet werden sollen. Mit XAML wird in dieser Arbeit
auf eine deklarative Sprache zurückgegriffen, die einfacher denn je beschreibt was angezeigt werden soll. Gestützt wird diese Sprache durch das WPF Framework. Im Zuge
dieser Arbeit wurden alle Benutzeroberflächen mit XAML erzeugt um die Vor- und
Nachteile dieser Technologie zu untersuchen.
Im Zuge dieser Arbeit wird außerdem auf die funktionale Sprache F# zurückgegriffen
um praxisnah und tiefergehend die wichtigsten funktionalen Konzepte zu beleuchten die
unter anderem auch hinter LINQ und XAML stecken. Mit der Sprache F#, welche ebenso wie C# ein Bestandteil des .NET Frameworks ist, wird eine kleine Bibliothek geschrieben, die es ermöglicht aus der Hauptanwendung bestimmte Bilder nachzubearbeiten. Dabei wird auch auf Konzepte der Parallelisierung von Codesegmenten eingegangen.
8.2
Wie hat sich die funktionale Programmierung eingebracht
Es wurden mehrere Szenarien betrachtet in denen funktionale Programmierung im .NET
Umfeld bereits jetzt zum Einsatz kommt. Praktisch geäußert hat sich dies auch in dem
Pixxlerprojekt.
8.2.1 LINQ zur Abfrage von Datenstrukturen
LINQ nutzt die funktionale Idee um Datenstrukturen unterschiedlicher Art intuitiver
und leichter durchsuchen und abfragen zu können. Der Einsatz lohnt sich nicht nur dahingehend, dass die Abfragen bereits zur Kompilierzeit auf Fehler überprüft werden,
sondern auch hinsichtlich der Anpassung des Quellcodes sowie der Lesbarkeit. Nicht
unterschätzt werden sollte außerdem der Aspekt, dass man stets mit typisierten Objekten
arbeiten kann.
55
Fazit
Da LINQ Statements bei der Kompilierung in .NET Code übersetzt werden, entsteht
kein Nachteil wenn man diese an Stelle von For- oder Foreach Konstrukten nutzt. Anders sieht es aus, wenn man LINQ zur Abfrage von Daten aus einer relationalen Datenbank nutzt. Diese müssen vor dem Abschicken an die Datenbank zunächst in T-SQL
Statements übersetzt werden, damit der SQL Server diese überhaupt interpretieren kann.
Der Overhead für diesen Prozess ist allerdings so minimal, dass er auf Grund der gegebenen Vorteile vernachlässigbar ist. Es sollte jedoch stets ein Auge auf die entstehenden
T-SQL Statements geworfen werden. Diese werden zwar durch die interne Interpretation zwar sehr gut optimiert, jedoch kann beim Entwickeln auch ein nicht optimales
LINQ Statement mit verschachtelten Abfragen zustande kommen. Im Pixxlerprojekt
kam LINQ sowohl zur Iteration von Listen als auch zur Abfrage von Daten aus der Datenbank zum Einsatz und hat sich als sehr komfortable Komponente erwiesen.
8.2.2 WPF & XAML zur Gestaltung von Oberflächen
Über XAML ohne das WPF Framework zu sprechen ist wie über C# als Sprache zu
sprechen ohne dabei das .NET Framework zu berücksichtigen. Nur in Kombination
ergeben sie eine Einheit, die eine Entwicklung von hübschen als auch dynamischen
Oberflächen ermöglicht. Durch den deklarativen Stil des XAML Codes ist es einfacher
denn je, eigene Kompositionen von Steuerelementen zu entwerfen und gleichzeitig eine
Verbesserung in den Arbeitsfluss eines Entwicklerteams zu bringen. Durch die eindeutige Trennung von Oberfläche und Anwendungslogik kann hier eine saubere Arbeitsaufteilung zwischen Gestaltern und Entwicklern erreicht werden. Durch die vielen neuen
Möglichkeiten, wie z.B. Trigger, können Gestalter den Entwicklern sogar einen gewissen Implementierungsaufwand abnehmen, da bereits in der Oberfläche ein Verhalten
festgelegt werden kann.
Der deutliche Nachteil von WPF ist wohl gleichzeitig auch der positive Aspekt. Es ist
ein sehr modernes und neues Framework. Das bedeutet im Klartext, dass es erst ab dem
Betriebssystem Windows XP SP3 eingesetzt werden kann. Desweiteren wird eine halbwegs moderne Grafikkarte mit Unterstützung ab Direct3D 9 benötigt um die Hardwarebeschleunigung ausnutzen zu können. Man sollte außerdem damit rechnen, mehr Arbeitsspeicher als bei gewöhnlichen formularbasierten Anwendungen zu verbrauchen.
Diese Tatsache macht außerdem den Einsatz von WPF in einer terminalen Serverumgebung schwierig.
56
Fazit
Der Einsatz von WPF & XAML hat sich im Pixxlerprojekt aber durchaus als Gewinn
ergeben. Nicht nur war die Gestaltung von hübschen Oberflächen nach einer gewissen
Einarbeitungszeit ein leichtes, sondern es hat sich auch nochmal der Vorteil des deklarativen Konzeptes, bei der Gestaltung von Oberflächen, gegenüber des objektorientierten
herausgestellt.
8.2.3 F# zur Implementierung von Kernfunktionalitäten
Die Sprache F# selbst hat sich als sehr mächtiges Werkzeug zur Entwicklung von Kernfunktionalitäten herausgestellt. Gerade durch den Einsatz von Funktionen höherer Ordnung wird die Flexibilität dieser Sprache sehr deutlich. Auch hat sich dadurch die
Parallelisierung von einzelnen Codesegmenten als sehr einfach herausgestellt.
Es lässt sich jedoch nicht leugnen, dass eine ausgiebige Einarbeitungsphase benötigt
wird um sowohl die Prinzipien der funktionalen Programmierung als auch sprachspezifische Eigenschaften von F# zu erlernen.
F# wurde im Pixxlerprojekt eingesetzt um sämtliche Berechnungen von Farb- und Effektfiltern parallelisiert durchzuführen. Die Tatsache, dass die F# Bibliothek über eine
einfache Referenz, auch aus einem C# Projekt, zugreifbar war hat den Nutzen nochmals
bestärkt.
8.3
Persönliches Fazit
Abschließend ist zu sagen, dass die funktionalen Aspekte in den einzelnen eingesetzten
Technologien die Entwicklung sehr stark in eine positive Richtung gewiesen haben. Ich
bin davon überzeugt, dass in den kommenden Jahren noch viele interessante Frameworks entwickelt werden, die ihre Wurzeln in den funktionalen Prinzipien wiederfinden.
Der Einsatz von LINQ, XAML und F# hat mich, als Entwickler, maßgeblich produktiver gemacht und hat den gesamten Prozess der Entwicklung spannender und sympathischer gestaltet.
Ich persönlich befürworte den Einsatz von LINQ in jedem Projekt. Die Vorteile überwiegen den nahezu kaum vorhandenen Nachteilen. Ich selbst werde im .NET Umfeld
kaum noch davon abzubringen sein, diese Technologie für die bessere Lesbarkeit und
einfachere Abfrage von Daten einzusetzen.
57
Fazit
Der Einsatz von WPF und XAML ist sicherlich immer im Kontext zu sehen. Gerade bei
der Betrachtung der Nachteile muss zuvor stets abgewägt werden ob sich die Umstellung auf eine modernere Technologie lohnt. Insgesamt denke ich jedoch, dass dieses
Framework, in der Art und Weise der Umsetzung, zukunftsweisend ist. Oberflächen
deklarativ zu beschreiben ist ein großer Gewinn in der Softwareentwicklung.
F# ist die erste funktionale Sprache, die ich in einem nicht akademischen Einsatzkontext
angewandt habe. Ich denke jedoch auch, dass die Einsatzgebiete für F# relativ speziell
sind. Die Gestaltung von Hilfsframeworks könnte ich mir durchaus vorstellen, nicht
jedoch die explizite Implementierung von Anwendungen. Ich persönlich würde nicht
unbedingt dazu tendieren mein Entwicklerteam eine F# Ausbildung zu geben. Man sollte hinter dem ganzen auch die Tatsache betrachten, dass bei einer solchen, eher selten
zum Einsatz kommenden, Sprache der Ersatz eines Entwicklers zu einem Problem werden kann. Dennoch halte ich es für ausgesprochen sinnvoll einem Entwicklerteam eine
Weiterbildung hinsichtlich der Paradigmen der funktionalen Programmierung zu geben.
Einige Aspekte finden nicht zu Letzt auch Anwendung in objektorientierten Programmen und viele Entwickler wissen vermutlich noch nicht einmal welches Paradigma sie
gerade bei der Entwicklung einsetzen.
58
<Literaturverzeichnis
9
Literaturverzeichnis
Beaulieu, J. (27. 12 2010). JFBEAULIEU.COM. Abgerufen am 26. 06 2013 von
JFBEAULIEU.COM: http://jfbeaulieu.com/en/component/content/article/53-dotnet/67wcfbasics
Crofton, G. (11 2010). Relentless Development. Abgerufen am 06 2013 von Relentless
Development: http://relentlessdevelopment.wordpress.com/2010/03/30/wcf-overview/
Fowler, M. (2004). MartinFowler.com. Retrieved 06 14, 2013, from MartinFowler.com:
http://www.martinfowler.com
Fowler, M. (2003). Patterns of Enterprise Application Architecture. Addison Wesley.
Freeman, E. F. (2004). Head First Design Patterns. O'Reilly.
Hudak, P. (2000). The Haskell School of Expression.
Hutton, G. (2002). FAQ - comp.lang.functional. Abgerufen am 29. 04 2013 von FAQ comp.lang.functional: http://www.cs.nott.ac.uk/~gmh/faq.html
Laucher, A. (2010). F# in Action. Mannig.
Lorenz, P. A. (03 2009). Higher-order Functions. .NET Pro , 119.
Microsoft. (05 2012). MSDN First-Class-Function. Abgerufen am 06 2013 von MSDN
First-Class-Function: http://msdn.microsoft.com/en-us/library/dd233158.aspx
Nathan, A. (2007). Windows Presentation Foundation Unleashed. Sams.
Seemann, M. (2012). Dependency Injection in .NET. Manning.
Smith, C. (2009). Programming F#. O'Reilly Media.
Team, M. (kein Datum). MSDN The Repository Pattern. Abgerufen am 18. 05 2013 von
MSDN The Repository Pattern: http://msdn.microsoft.com/en-us/library/ff649690.aspx
Team, P. a. (06 2012). MSDN PRISM. Abgerufen am 26. 05 2013 von MSDN PRISM:
http://msdn.microsoft.com/en-us/library/gg406140.aspx
Wikipedia. (12. 04 2012). Wikipedia MVVM Muster. Abgerufen am 14. 06 2013 von
Wikipedia MVVM Muster: http://de.wikipedia.org/wiki/Model_View_ViewModel
Erklärung
Ich versichere, die von mir vorgelegte Arbeit selbständig verfasst zu haben. Alle Stellen, die wörtlich oder sinngemäß aus veröffentlichten oder nicht veröffentlichten Arbeiten anderer entnommen sind, habe ich als entnommen kenntlich gemacht. Sämtliche
Quellen und Hilfsmittel, die ich für die Arbeit benutzt habe, sind angegeben. Die Arbeit
hat mit gleichem bzw. in wesentlichen Teilen noch keiner anderen Prüfungsbehörde
vorgelegen.
Köln, 07.07.2013
Michael Morbach