Festlegung von Zielversionen

Erstellen von Anwendungen mit festgelegten Zielversionen für den Desktop, für Prism und Windows Phone 7

Bill Kratochvil

Microsoft bietet leistungsstarke Frameworks zum Entwickeln von skalierbaren und erweiterbaren Anwendungen. Einige aktuelle Projekte – Prism, Managed Extensibility Framework (MEF) und Unity – verfügen über Funktionen für die Festlegung von Zielversionen. Durch die Festlegung von Zielversionen ist es möglich, mit einer einzigen Codebasis zu arbeiten, die von Desktop- sowie Silverlight- und Windows Phone 7-Plattformen gemeinsam genutzt wird. Diese kostenfreien Tools verfügen über eine umfassende Dokumentation mit Ersten Schritten, Labs und zahlreichen Codebeispielen, die Ihnen den Einstieg erleichtern. Damit Sie aus den Arbeitsstunden, die Sie mit Recherche und Lernen verbracht haben, eine maximale Rendite ziehen können, müssen Sie verstehen, dass für diese Tools und Dokumentationen eine neue Denkweise erforderlich ist.

Prism, MEF und Unity arbeiten mit fortschrittlichen Mustern der Softwareentwicklung, die für die meisten von uns Entwicklern schwer zu erlernen sind. Und zwar so schwer, dass eine Rendite unerreichbar scheint. Sie erfordern nicht nur eine neue Denkweise von Ihnen, sondern auch Kenntnisse der betreffenden Tools und Muster. Fehlt einer dieser Bereiche, wirkt sich das nachteilig auf die Lernkurve und die Anwendungsstabilität aus. Wenn Sie allerdings bereits mit einigen grundlegenden Konzepten vertraut sind, verstehen Sie nicht nur die Tools und die Dokumentation, sondern können glücklicherweise eine beliebige Prism-, MEF- oder Unity-Anwendung öffnen, in dieser navigieren und sie so kennenlernen. Oder Sie nutzen aus den bekannten Optionen diejenige erneut, die zum Erstellen Ihrer Anwendungen mit festgelegten Zielversionen am Besten geeignet ist. Diese Konzepte beinhalten Project Linker, Abhängigkeitsinjektion (Dependency Injection, DI), gemeinsame Nutzung von Code und Architekturmustern.

Project Linker

Es ist wichtig, zu verstehen, dass Projekte nicht über Plattformen hinweg gemeinsam genutzt werden können. Desktopprojekte können nicht auf Silverlight-Projekte verweisen, und Silverlight-Projekte können nicht auf Desktopprojekte verweisen. Ebenso kann ein Windows Phone 7-Projekt nicht auf Silverlight- oder Desktopprojekte verweisen. Sie nutzen jedoch keine Projekte gemeinsam, sondern Code. Daher müssen Sie für jede Plattform ein Projekt anlegen und die Dateien „verknüpfen“.

In Visual Studio werden verknüpfte Dateien durch ein Verknüpfungssymbol auf dem Dateisymbol gekennzeichnet. Physikalisch sind sie auf Ihrer Festplatte nicht vorhanden.

Beachten Sie in Abbildung 1, dass nur das Phone-Projekt (Quellprojekt) über Code verfügt. Das Desktop- und das Silverlight-Projekt (Zielprojekte) weisen verknüpfte Dateien auf, ihre Ordner sind also leer.

Only Source Projects Have Code

Abbildung 1 Nur in Quellprojekten ist Code vorhanden

Im Prism-Projekt (compositewpf.codeplex.com) ist eine projektverknüpfende Anwendung enthalten. Ohne dieses Tool könnte sich die Verwaltung von Anwendungen mit festgelegten Zielversionen schnell als entmutigende Aufgabe erweisen. Mit dem Tool können Sie ein Quellprojekt und eine Verknüpfung zu den Zielprojekten einrichten, sodass jedwede Aktion, die Sie an der Quelle ausführen (z. B. Ordner und Dateien hinzufügen, umbenennen und löschen), in den Zielprojekten dupliziert wird.  

Bei Anwendungen mit festgelegten Zielversionen sind Benennungskonventionen ausgesprochen wichtig. Die verknüpften Projekte sollen abgesehen vom Suffix, der die Plattform angibt, alle dieselbe Benennung nutzen. Im Sinne der Ordnerstruktur ist es am Besten, das Projekt mit einem Suffix anzulegen (z. B. „Gwn.Infrastucture.Desktop“) und diesen dann in der Projektkonfiguration aus dem Standardnamespace zu entfernen. Dies wird in Abbildung 2 veranschaulicht.

Remove the Suffix from the Default Namespace After Creating the Project

Abbildung 2 Entfernen des Suffix aus dem Standardnamespace nach der Projekterstellung

Damit wird sichergestellt, dass die Ordnerstrukturen unzweideutig sind und keine Probleme beim Erstellen neuer Projekte (mit derselben Benennung) für die anderen Plattformen verursachen. Das Entfernen des Suffix vom Standardnamespace ist ein wichtiger Schritt. Da der Code auf mehreren Plattformen gemeinsam genutzt werden soll, darf er keinen plattformspezifischen Namespace aufweisen, andernfalls führt dies während der Kompilierzeit zu Fehlermeldungen der anderen Plattformen.

Zudem hilft Ihnen die vorangegangene Anleitung dabei, während der Entwicklung einen kühlen Kopf zu bewahren. Falls Sie nämlich versehentlich einen Assemblyverweis auf eine andere Plattform einbauen – beispielsweise verfügt meine in Abbildung 2 veranschaulichte Desktopanwendung über einen Verweis auf eine Silverlight-Assembly –, erhalten Sie eine verwirrende Fehlermeldung (da sie im Namespace vorhanden ist). Wenn Sie einen Plattformsuffix verwendet haben, entlarvt ein kurzer Blick auf die Projektverweise den Schuldigen.

Microsoft bietet eine ausgezeichnete Dokumentation für Project Linker. Sie finden diese untermsdn.microsoft.com/library/dd458870. Zudem können Sie mithilfe des Bing-Suchmoduls nach „Project Linker“ suchen und weitere wertvolle Ressourcen finden. Dazu zählt auch ein Link von mir (also BillKrat), hinter dem sich ein Webcast verbirgt, der Sie durch den Prozess der Projektverknüpfung leitet. * (Hinweis: Wenn Sie Visual Studio 2010 verwenden, ist der* Installationsprozess deutlich einfacher, als in der Dokumentation dargelegt. Wählen Sie in den Menüoptionen „Extras“ | „Erweiterungs-Manager“ aus, suchen Sie nach „Project Linker“, und installieren Sie das Tool.)

Abhängigkeitsinjektion

Um Prism, MEF und Unity verstehen zu können, sind Kenntnisse über die Abhängigkeitsinjektion (Dependency Injection, DI) erforderlich. Ohne dieses Wissen wirken Dokumentation und Codebeispiele nicht nachvollziehbar, und der Lernprozess wird viel schwieriger sein als nötig. Wir beschränken das Thema der Abhängigkeitsinjektion auf den Unity-Container (unity.codeplex.com). Wenn Sie das DI-Konzept verinnerlicht haben, lesen Sie am Besten auch das MEF-Konzept (mef.codeplex.com).

Was ist ein DI-Container? Auf allgemeiner Ebene kann ein DI-Container als überglorifizierte Wörterbuchklasse betrachtet werden, mit der Klassen erstellbar sind. In diesem Wörterbuch lassen sich Schnittstellen und deren Implementierung (oder Instanzen) registrieren – z. B. „dictionary.add(IFoo,Foo)“. Wenn Sie den Container anweisen, die Schnittstelle „IFoo“ anzulegen, wird das Wörterbuch durchsucht. Dann wird ein Typ gefunden („Foo“) und instanziiert. Nach der Instanziierung von „Foo“ wird überprüft, ob „Foo“ über aufzulösende Schnittstellen verfügt. Diese Abfolge wird solange wiederholt, bis alle untergeordneten Elemente erfolgreich erstellt sind. Nach der Fertigstellung gibt der Container eine Instanz von „Foo“ zurück.

Konstruktor- und Setter-Injektion In der Regel wird eine Klasse von Codeanweisungen instanziiert, damit sie ihre Ressourcen nutzen kann (Abbildung 3, Zeile 18).

Constructor and Setter Injection

Abbildung 3 Konstruktor- und Setter-Injektion

Mit der Abhängigkeitsinjektion werden die Klassen extern aufgelöst (instanziiert), und die Instanzen werden vom DI-Container in die Klasse eingefügt. Unity unterstützt zwei Injektionsarten: Die Konstruktor- und die Setter-Injektion. Bei der Konstruktorinjektion (Abbildung 3, Zeile 25) erstellt der Container dynamisch die Klasse „MyTaskConstructorInjection“, löst „IFoo“ auf und übergibt das Element als Konstruktorparameter. Wenn der Unity-Container die Klasse erstellt und Parameter eingefügt hat, durchsucht er die Klasse nach Abhängigkeitsattributen (Abbildung 3, Zeile 33) und löst die Eigenschaften auf. Da die Setter-Injektion erst nach der Konstruktorinjektion stattfindet, können Sie keine Eigenschaftenverweise auf das Abhängigkeitsattribut im Konstruktor erstellen.

Registrieren von Typen Damit die Schnittstellentypen (wie z. B. „IFoo“) vom Container aufgelöst werden können, muss zunächst die Implementierung im Container registriert werden (siehe Abbildung 4, Zeilen 49 und 50).

Registering the Implementation with the Container

Abbildung 4 Registrieren der Implementierung im Container

In der Regel erfolgt die Registrierung im Bootstrapper, Modul oder Presenter/Controller der Anwendung, da es sich hier meist um die Initialisierungsklassen handelt. Sobald die Schnittstellen registriert sind, erkennt der Container, wie er eine in der Abhängigskeitskette auftretende Schnittstelle auflösen kann.

Erweiterbare und skalierbare Anwendungen In diesem Artikel unterstellen wir der Einfachheit halber, dass die Klasse „Foo“ eine Datenzugriffsschicht (Data Access Layer, DAL) ist, die von Ihrem Unternehmen für den Zugriff auf die SQL Server-Datenbank verwendet wird. Sie wird von Hunderten Modulen und zahlreichen Anwendungen genutzt. Das Management hat gerade eine Lizenzvereinbarung über Cloud-Dienste mit Microsoft abgeschlossen und Sie darüber informiert, dass ein Upgrade für die Anwendungen erforderlich ist, damit diese cloudfähig werden. Im Rahmen der Migration wird „Foo“ weiterhin von einigen Anwendungen genutzt (für die SQL Server-Datenbank), wohingegen andere in die Cloud verlagert werden. Für die Anwendungen, die in die Cloud migriert werden, müssen Sie für den Fall, dass die Datenmigration aus irgendwelchen Gründen nicht erfolgreich verläuft, schnell wieder auf die „alte“ Datenbank umschalten können. Wie lange würden Sie dafür brauchen?

Mithilfe der Abhängigkeitsinjektion sind dafür nur ein paar Zeilen Code nötig. Hier kommt der Pseudocode:

var  isCloud =  GetCloudConfigurationFromConfigFile();
if(isCloud)
  container.RegisterType<IFoo, FooCloud>();
else
  container.RegisterType<IFoo, Foo>();

Da Ihre Unternehmensanwendungen über Integrationstests verfügen, können Sie die testgesteuerte Entwicklung (Test-Driven Development, TDD) auf die neue Datenzugriffsschicht „FooCloud“ anwenden. Sobald die testgesteuerte Entwicklung abgeschlossen ist, können Sie die Bootstrapper der einzelnen Anwendungen mit dem voranstehenden Code aktualisieren und bereitstellen, wobei KEINE Regressionstests für die Anwendungen erforderlich sind, die weiterhin die SQL Server-Datenbank (Klasse „Foo“) nutzen (da keine Codeänderung erfolgt ist).

Die folgende Geschäftslogikschicht (Business Logic Layer, BLL) könnte potenziell von Hunderten Presentern/Controllern genutzt werden. Dieser Codeabschnitt beschäftigt sich strikt mit Domänenobjekten, und wir müssen diesen – oder anderen Code, der diese Geschäftslogikschicht verwendet – nicht bearbeiten, denn wir schreiben Code für eine Schnittstelle der Datenzugriffsschicht („IFoo“):

public class FinancialDataBll  : IFinancialDataBll
   {
     [Dependency]
       public  IFoo Dal {get;set;}

       public  IEnumerable<FinancialEntity> 
         GetFinanicalData(object sender, EventArgs e)
       {
         // Validate event arguments prior to calling data layer
         var returnResults = Dal.GetFinancialData(sender,e);
         // Handle errors with friendly messages as applicable
         return returnResults;
       }
   }

Wenn Sie den Code eng an Klasse „Foo“ in Abbildung 3, Zeile 18, gekoppelt hätten, wären Ihre Anwendungen nicht so erweiterbar, und der Migrationsprozess wäre in Bezug auf Coding und Tests deutlich kostenintensiver.

Schichten Für eine effektive Nutzung der Abhängigkeitsinjektion ist eine ordnungsgemäße Struktur der Geschäftslogikschicht und der Datenzugriffsschicht erforderlich. Die Geschäftslogikschicht sollte keine Kenntnisse der Komponenten der Datenzugriffsschicht haben – beispielsweise Verbindungszeichenfolgen, SQL-Anweisungen, Dateihandles wie XML-Dateien usw. Ebenso sollte die Darstellungsschicht (Komponenten der Benutzeroberfläche) keine Kenntnis der Datenzugriffsschicht haben, sondern nur die Geschäftslogikschicht kennen. * (Hinweis: Ihre Geschäftslogikschichten können ebenso wie die Datenzugriffsschicht leicht ausgelagert werden, wenn die Darstellungsschicht die Abhängigkeitsinjektion zur Auflösung der Geschäftslogikschichten verwendet. Falls Sie eine Umgestaltung unter Einbeziehung von nur einer Anwendung ausführen wollen, ist das von unschätzbarem Wert.)*

Ereignisaggregation Die Kommunikation zwischen entkoppelten Komponenten kann zu Problemen führen. Im Beispiel in Abbildung 5 enthält Objekt D eine Dropdownliste, mit der Benutzer die Währung ändern können, z. B. von US-Dollar in Euro. 

Communication Between Decoupled Objects

Abbildung 5 Kommunikation zwischen entkoppelten Objekten

Die Objekte B und F beziehen sich auf die derzeit ausgewählte Währung, bei einer Änderung der Währung müssen diese neu berechnet werden. Eine mögliche Lösung bestünde darin, das Ereignis an A weiterzuleiten (wobei A Objekt B abonniert, B Objekt C abonniert, und C Objekt D abonniert), und dann die Informationen von A an E zu tunneln, wobei E wiederum die Daten an F tunnelt. Je weiter die Anwendung wächst, desto komplizierter wird dieser Prozess. Diese Komplexität würde sich auf die Tests ausweiten, da alle betroffenen Bereiche einem Regressionstest unterzogen werden müssten, um zu prüfen, ob irgendwo im Pfad Bedingungsanweisungen hinzugefügt worden sind.

Um für diese Lösung Speicherverluste zu verhindern, müssten wir gewährleisten, dass „IDisposable“ auf allen betroffenen Objekten implementiert und alle Ereignisabonnements entfernt worden sind. Dadurch entstünde weitere Komplexität, die ebenfalls verwaltet werden müsste.

Prism Die Ereignisaggregation ist einer der vielen Bereiche, in der Prism herausragende Leistungen bietet. Mit Prism können Sie ein Ereignis in D einfach mithilfe folgender Syntax veröffentlichen:

aggregator.GetEvent<MyEvent>().Publish(MyPayload)...

Die Objekte B und F würden dieses Ereignis abonnieren. Nachdem Sie also ein paar Codezeilen geschrieben hätten, könnten Sie sich auf die Geschäftslogik konzentrieren, anstatt sich in den Einzelheiten der grundlegenden Infrastruktur zu verlieren. Ein Beispielabonnement von B und F wird hier dargestellt:

[Dependency]
public ILoggerFacade Logger {get;set;}

private  IEventAggregator _eventAggregator;
[Dependency]
public  IEventAggregator EventAggregator 
{
  get { return _eventAggregator; }
  set { 
    _eventAggregator=value;
    OnEventAggregatorSet();  // Subscribe to events in this method
  }
}

Ein weiteres Beispiel:

public  MyConstructor(IEventAggregator aggregator){
  aggregator.GetEvent<MyEvent>().Subscribe(HandleMyEvent);  
}
public void HandleMyEvent(MyEventArgs e){
  Logger.Log("HandleMyEvent is handling event in Foo", Category.Debug, Priority.None);
 // Do something useful
}

Gemeinsames Nutzen von Code und XAML

Bevor Zertifikatsanforderungen für die elektronische Patientenakte (ePA) eingeführt wurden (und die ePA-Entwicklung somit für kleine Unternehmen unfinanzierbar wurde), schrieb ich eine ePA-Anwendung für ein kleines, im Gesundheitsbereich tätiges Unternehmen. Zu Beginn ging es um ein Windows Mobile-PDA und eine Website für die akkurate Verwaltung von Patientenversorgung und Abrechnungen. Das PDA verfügte über die gesamte Geschäftslogik, und die Website bot eine Oberfläche für die Mitarbeiter der Abrechnungsabteilung. Nach und nach wurden die PDA-Features in die Website migriert. Es wurde erwartet, dass auch alle Geschäftsregeln und -verhaltensweisen ebenfalls migriert werden würden. Um das Ganze zu verkomplizieren, wurde ich danach mit der Erstellung einer Desktopanwendung für die Tablet PCs und Laptops beauftragt. Innerhalb eines Jahres erweiterte sich der Projektumfang von einem PDA auf alle drei Plattformen. Ich musste mich mit drei unterschiedlichen Codebasen auseinandersetzen, von denen jede plattformspezifische Varianten aufwies, sodass die gemeinsame Nutzung der Geschäftslogik nicht gerade einfach war.

Als im Prism-Projekt der projektverknüpfende Project Linker entwickelt wurde, und das Team von Patterns & Pratices sich bemühte, eine Umgebung mit festgelegten Zielversionen zu unterstützen (wobei plattformübergreifend dieselben Namespaces genutzt werden sollten), war ich sofort mit von der Partie. Schließlich wusste ich die angestrebte Zielrichtung umfassend zu schätzen. Mithilfe der Festlegung von Zielversionen hätte ich das ePA-Projekt in einem Bruchteil der Zeit und mit minimalen Kosten für Entwicklung, Upgrades und Aktualisierungen für meinen Kunden entwickeln können.

Code Viele Überlegungen müssen auf die zu unterstützenden Plattformen verwendet werden. Es ist wichtig, dass die einzelne Codebasis auf die Plattform abgestimmt ist, die am wenigsten unterstützt wird. Wenn Sie beispielsweise nur für die Silverlight- und Desktopumgebung programmieren, legen Sie die Silverlight-Projekte als Quelle fest, und die Desktopprojekte werden dann mit diesen verknüpft. Wenn Sie hingegen Windows Phone 7, Silverlight und den Desktop unterstützen möchten, müssen die Windows Phone 7-Projekte als Quelle ausgewählt werden.

Das ist sehr wichtig, da Sie Ihre Zeit nicht damit verschwenden möchten, Code zu schreiben, der auf den anderen Plattformen nicht kompiliert werden kann. Angenommen, Sie entwickeln für die Windows Phone 7-Plattform. Mithilfe von IntelliSense sehen Sie auf einen Blick, welche Framework-Features verfügbar sind. Sie können dann Ihren Code in dem Vertrauen schreiben, dass dieser auf den anderen Plattformen kompilierbar ist.

Außerdem wird empfohlen, dass Sie einen Konfigurationsstandard für die „Symbole für bedingte Kompilierung“ festlegen (siehe Abbildung 6, die markierten Bereiche zeigen die Standardeinstellungen an).

Set Compiler Symbols for Each Platform

Abbildung 6 Festlegen von Kompilierungssymbolen für die einzelnen Plattformen

Für mich stellt sich das als problematisch heraus, da „SILVERLIGHT“ sowohl in Windows Phone 7- als auch Silverlight-Projekten gemeinsam genutzt wird. Um Code ausschließlich für Silverlight 4 zu schreiben, gehe ich folgendermaßen vor:

#if SILVERLIGHT
#if !WINDOWS_PHONE
  // My Silverlight 4-specific code here 
#endif
#endif

XAML XAML lässt sich überraschend einfach wiederverwenden, besonders zwischen Silverlight- und Desktopanwendungen. Der Trick besteht darin, in den XAML-Code keinerlei Assemblyverweise einzubinden. Ich erreiche dies, indem ich eine Wrapperklasse (siehe Abbildung 7, Bereich links oben) innerhalb des lokalen Projekts anlege, die als Unterklasse für die tatsächliche Klasse in der externen Assembly fungiert.

Create a Wrapper Class for the Current Project

Abbildung 7 Erstellen einer Wrapperklasse für das aktuelle Projekt

Beachten Sie, dass die Klasse „UserControlBase“ programmgesteuert die plattformspezifische Ressourcendatei lädt, in der Konverter, Stile und Vorlagen enthalten sind. Diese ermöglichen es mir, mit nur minimalen XAML-Deklarationen zu arbeiten, die sich möglicherweise auf die gemeinsame Nutzung auswirken könnten.

Architekturmuster

Wenn Sie 10 Programmierer in einen Raum setzen und mit derselben Entwicklungsaufgabe betrauen, erhalten Sie wahrscheinlich 10 verschiedene Möglichkeiten, die Aufgabe erfolgreich umzusetzen. Als Entwickler verfügen wir über unterschiedliche Erfahrungsstufen in Bezug auf Coding und Architektur, die sich in unserem geschriebenen Code widerspiegeln (wodurch unsere Unternehmensanwendungen schwer nachvollziehbar und kompliziert zu warten sind). Architekturmuster und -standards bieten uns eine gemeinsame Erfahrungsplattform, sodass die genannten 10 Programmierer mit in etwa vergleichbarem Code aus dem Raum kommen.

Mit Windows Presentation Foundation (WPF), Prism, MEF und Unity wird eine bisher einzigartige Entwicklungsumgebung eingeführt. Die Hinzufügung von Throws mit festgelegten Zielversionen in einer erhöhten Komplexität – besonders bei effizienter Nutzung und Minimierung von plattformspezifischem Code. Bei neuen Technologien in einer flexiblen Welt müssen die Muster regelmäßig aktualisiert werden, um die angebotenen technologischen Innovationen wirksam nutzen zu können. Der Vielzahl und den Kombinationen der Muster werden Sie sich beim Durchsehen der Beispiele, die von diesen leistungsstarken Tools bereitgestellt werden, kaum entziehen können.

Wenn Sie Muster nicht erkennen, werden Sie auch die Tools, die Dokumentation sowie die Codebeispiele nur schwer verstehen können. Schlimmer noch: Sie könnten womöglich diese effizienten Tools auf eine Art und Weise nutzen, die dem beabsichtigten Zweck zuwiderläuft, und somit ein Risiko für den Erfolg des Projekts darstellen. In Anhang B der Prism-Dokumentation werden umfangreiche Informationen über anwendbare Muster geboten. Ich werde die Entwicklung der Darstellungsmuster im nächsten Abschnitt zusammenfassen.

Model View Controller (MVC)-Anwendungsmodell Dieses Modell ist als Antwort auf Probleme, die mit dem MVC-Konzept auftraten, entstanden. Dies betrifft im Detail die Masse an Geschäftslogik und Benutzeroberflächenzuständen (wie z. B. Textfarbe, ein ausgewähltes Element in einer Liste, die Sichtbarkeit der Steuerelemente usw.), mit der die Domänenobjekte „überschwemmt“ werden. Zudem kann hier die Ansicht direkt aktualisiert werden (was mit dem MVC-Konzept nicht möglich ist).

Das Anwendungsmodell wird zwischen „View“ (Ansicht) und „Model“ (Modell) eingefügt und umfasst die Geschäftslogik, die zum Verwalten von Verhalten, Status und Synchronisierung mit dem Modell erforderlich ist. „View“ wird dann an das Anwendungsmodell gebunden (nicht an „Model“).

MVC-Präsentationsmodell Der Hauptunterschied zwischen Präsentations- und Anwendungsmodell besteht in der Möglichkeit, die Ansicht zu aktualisieren. Das Präsentationsmodell folgt den MVC-Richtlinien und gestattet keine direkte Aktualisierung der Ansicht.

Model-View-ViewModel (MVVM) John Gossman, MVVM-Entwickler, sagt dazu Folgendes:

„Bei der Benennung bin ich zurzeit der Ansicht, dass es sich bei dem [MVVM-]Muster um eine WPF-spezifische Version des Präsentationsmodells handelt. Das Präsentationsmodell ist deutlich einfacher anzugeben und zu schreiben, außerdem auch viel bekannter. Aber derzeit halte ich gedanklich an der MVVM-Terminologie fest, um die WPF-Artigkeit zu erhalten und sämtliche Interpretationskonflikte zu verhindern.“

Model View Presenter (MVP) MVP bietet die Lösung für die Einschränkungen von MVC sowie der Muster des Anwendungs- und des Präsentationsmodells. Mit dem MVP-Konzept ist der Entwickler in der Lage, sowohl „View“ (Ansicht) als auch „ViewModel“ (Ansichtsmodell) zu steuern. Windows-Entwickler können den Luxus genießen, mit intelligenten Steuerelementen zu arbeiten, sodass der Unterschied zwischen MVC und MVP vielen von uns vielleicht nicht ganz klar ist. Mit ein wenig Recherche werden Sie feststellen, dass Windows den MVC-Controller überflüssig gemacht hat, da das Betriebssystem und die Steuerelemente die meisten der Controller-Funktionen enthalten.

Die Notwendigkeit der weiteren Entwicklung nach MVC ist nicht nur auf den nicht länger benötigten Controller beschränkt. Andere Faktoren sind hier die bereits genannten Einschränkungen im Anwendungs- und im Präsentationsmodell.

Hier sehen Sie eine Übersicht der MVP-Komponenten:

Model Im Modell sind die Daten enthalten, an die „View“ gebunden wird.

View In der Ansicht werden die Inhalte von „Model“ mithilfe der Datenbindung angezeigt. Diese Unterscheidung ist sehr wichtig, da ein „View“ von mehreren „Presentern“ genutzt werden kann. (Hinweis: Im Jahre 2006 hat Martin Fowler das MVP-Muster in zwei deutlich unterscheidbare Muster aufgegliedert: „Supervising Controller“ (überwachende Steuerung) und „Passive View“ (passive Ansicht). Der Hauptunterschied zwischen beiden Mustern besteht in der Datenbindung. Liegt eine Datenbindung vor, wird das Muster als „Supervising Controller“ bezeichnet [und übernimmt die bereits genannten Verantwortlichkeiten]. Liegt hingegen keine Datenbindung vor, wird das Muster als „Passive View“ bezeichnet, und die Verantwortlichkeiten werden verlagert. So obliegt es dann ausschließlich „Presenter“, für die beständige Synchronisierung zwischen Ansicht und Modell zu sorgen.)

Presenter „Presenter“ sind sowohl „View“ als auch „Model“ bekannt, er ist für die Geschäftslogik und die Füllung des Modells verantwortlich.

Model View Presenter, ViewModel (MVPVM) Dieses (namenlose) Muster ist in einem frühen Entwurf des Prism-Projekts entwickelt worden, es kombiniert die Leistung von MVP und MVVM:

// Load the view into the MainRegion 
  // after the presenter resolves it
  RegionViewRegistry.RegisterViewWithRegion("MainRegion", () => 
    presenter.View);

Diese einfache Codezeile in Verbindung mit Erfahrungen, die das Patterns & Practices-Team bei der Projektarbeit mit Prism, Unity, Smart Client Software Factory (SCSF) und Web Client Software Factory (WCSF) gemacht hat, bildet das Muster, das ich als MVPVM bezeichne. Mit diesem Muster ist „Presenter“ zuständig für die Auflösung (Instanziierung) von „ViewModel“, „View“ sowie benötigten Komponenten der Geschäftslogikschicht. Die erforderlichen Methoden der Geschäftslogikschicht werden von „Presenter“ aufgerufen (siehe Abbildung 8), um „ViewModel“ mit den Daten zu versorgen, die mit „View“ angezeigt werden sollen.

Model-View-Presenter, ViewModel  (MVPVM) and Associated Components

Abbildung 8 Model View Presenter, ViewModel (MVPVM) und zugehörige Komponenten

Da die Geschäftslogik in „Presenter“ verbleibt, kann „ViewModel“ problemlos von anderen „Presentern“ wiederverwendet werden. Zudem kann „View“ von „Presenter“ direkt aktualisiert werden und so die komplexe Bindungslogik (falls nötig) vermeiden. Dies gilt besonders, wenn ein Steuerelementverweis erforderlich ist. Das MVPVM-Konzept hebt alle Einschränkungen von Anwendungsmodell, Präsentationsmodell und MVVM (wie von unseren bekannten Architekten skizziert) für die Prism- und DI-Umgebung mit festgelegten Zielversionen auf.

Die vorangegangene Codezeile finden Sie im Bereich „SecurityModel“ in unserem Open Source-Projekt „Password Manager“ unterPasswordMgr.CodePlex.com. In diesem Projekt mit festgelegten Zielversionen nutzen Desktop-, Silverlight- und Windows Phone 7-Anwendungen gemeinsam eine einzige Codebasis, zudem verwenden die Silverlight- und Desktopanwendungen gemeinsam dieselbe XAML.

* (Hinweis: Zum Zeitpunkt dieser Artikelerstellung gibt es keine offizielle Unterstützung von DI-Containern auf der Windows Phone 7-Plattform. Für dieses Projekt habe ich „ContainerModel“ aus dem Windows Mobile 6.5-Projekt heruntergeladen [mobile.codeplex.com], die Schnittstelle „IUnityContainer“ implementiert* [aus dem Unity-Projekt unterunity.codeplex.com] und mehrere Unity-Komponententests sowie die testgesteuerte Entwicklung ausgeführt, bis alle Komponententests erfolgreich verlaufen sind. Mit dem neu entwickelten DI-Container, der ein Unity-Erscheinungsbild aufweist, ist es mir gelungen, das Prism-Projekt zu portieren – das Projekt „Password Manager“ verfügt über mehr als 800 Komponententests, die Kompilierung erfolgt mit null Fehlern, Warnungen und Meldungen.)

Model Für gespeicherte Modellobjekte ist ausschließlich die Datenzugriffsschicht zuständig. Zudem ist sie für die Übertragung der gespeicherten Modellobjekte an die Domänenobjekte zuständig (dabei handelt es sich um das Modell repräsentierende Entitäten, die sich im Umfang aller Anwendungsschichten des Unternehmens befinden). * (Hinweis: Die Geschäftslogikschicht hat keinerlei Kenntnis von der Darstellungsschicht oder dem Modell, sie kommuniziert über eine Schnittstelle ausschließlich mit der Datenzugriffsschicht.)*

View Die Ansicht beobachtet das Ansichtsmodell „ViewModel“ (Datenbindung) und zeigt die Daten bei Bedarf in „ViewModel“ an. Ein „View“ (Ansicht) kann nur ein „ViewModel“ haben, jedoch kann „ViewModel“ von mehreren Ansichten gemeinsam genutzt werden.

Presenter „Presenter“ ist zuständig für die Instanziierung von „View“ und „ViewModel“. Zudem legt er den Datenkontext der Ansicht fest, schreibt Ereignisse und verarbeitet die Geschäftslogik für alle Ereignisse und Verhaltensweisen (Mausklick, Schaltflächenauswahl, Menüauswahl usw.).

„Presenter“ verwendet (gemeinsam genutzte) Geschäftslogikschichten, um die Geschäftslogik zu verarbeiten und CRUD-Vorgänge (Create, Read, Update und Delete – Erstellen, Lesen, Aktualisieren und Löschen) für gespeicherte Modelldaten auszuführen. * (Hinweis: Geschäftslogikschichten eignen sich gut für Komponententests und können problemlos mithilfe der Abhängigkeitsinjektion (DI, Dependency Injection) ausgetauscht werden.)*

„Presenter“ verfügt über die MVP-Funktion für die direkte Aktualisierung von „View“, den zugehörigen Steuerelementen und „ViewModel“.

ViewModel Das Ansichtsmodell kann von zahlreichen „Views“ verwendet werden, sodass die gemeinsame Nutzung bei der Entwicklung bedacht werden sollte.

Die Kommunikation zwischen „ViewModel“ und „Presenter“ kann entweder über die Ereignisaggregation (bevorzugt) oder über Schnittstellen (für die generische Funktionalität) stattfinden. In der Regel wird dazu eine „ViewModel“-Basisklasse benötigt (die mit der „Presenter“-Basisklasse kommuniziert). Die Kommunikation ist erforderlich, da die gesamte Geschäftslogik von „Presenter“ verarbeitet wird, die Befehle und Ereignisse von „View“ aber an „ViewModel“ gebunden sind. Diese Ereignisse müssen für die Verarbeitung auf lose gekoppelte Weise an „Presenter“ übergeben werden.

Leistung und Vielseitigkeit

Mit den gerade erlernten Grundlagen sollte Ihnen das in Abbildung 9 veranschaulichte Codesegment nicht mehr fremd vorkommen.

Module Registrations

Abbildung 9 Modulregistrierungen

Die Leistungen und die Vielseitigkeit der vom Patterns & Pratices-Team bereitgestellten Tools sind offensichtlich und versprechen eine erhebliche Rendite. Sie abstrahieren die Komplexität der Infrastruktur und ermöglichen es Ihnen, skalierbare und erweiterbare Anwendungen mit loser Kopplung zu erstellen. Diese Anwendungen können eine gemeinsame Codebasis nutzen, sodass Sie bei gewährleisteter Zuverlässigkeit und Stabilität mehr Arbeit mit weniger Ressourcen erledigen können.

Die effektive Nutzung von Schichten (Darstellungsschichten, Geschäftslogikschichten und Datenzugriffsschichten) bietet eine deutliche Trennung der Zuständigkeiten, damit Sie Ihre Komponenten mehrfach modul- und anwendungsübergreifend verwenden können. Dadurch werden die Kosten für Tests, Neuentwicklungen, Upgrades und Aktualisierungen minimiert.

Am wichtigsten ist jedoch, dass Sie produktive und flexible Teams zusammenstellen können, die sich mit ihren Erfahrungen und Domänenkenntnissen durch eine Welt der beständigen Änderungen bewegen. Die Teams können ihr Talent auf die Geschäftslogik (nicht auf Infrastruktur) in einer einzigen Codebasis konzentrieren, was es ihnen ermöglicht, Anwendungen für die Plattformen Windows Phone 7, das Web, Tablet PCs oder den Desktop zu entwickeln.

Bill Kratochvil ist unabhängiger Auftragnehmer und leitender Technologie- und Architekturexperte eines Eliteentwicklerteams, das für ein führendes Unternehmen der Gesundheitsbranche an einem vertraulichen Projekt arbeitet. Der Hauptsitz seines eigenen Unternehmens – Global Webnet LLC – befindet sich im texanischen Amarillo.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Christina Helton und David Kean