Im ersten Teil dieser Serie haben wir einen Blick darauf geworfen, welche Möglichkeiten sich uns überhaupt bieten Code zwischen Windows 8 und Windows Phone 8 wiederzuverwenden. Wir haben die Portable Libraries kennengelernt und deren Einschränkungen erfahren. Im zweiten Teil haben wir uns unterschiedliche Methoden angesehen, wie wir durch geschickte Dateiverwaltung und ein paar Visual Studio Features Quellcode Artefakte zwischen mehreren Projekten teilen können. Natürlich können wir der Aufgabe plattformübergreifen Code zu entwickeln auch durch die Komponenten-Design Brille begegnen.

Vererbung

Wenn wir für zwei oder mehr Plattformen gleichzeitig entwickeln wollen (oder sollen), können wir diese Aufgaben ja auch wie folgt formulieren:

Wir entwickeln gleiche oder für mehrere Plattformen, wobei manche der Funktionen sich in der Implementierung unterscheiden (also plattformspezifisch sind), ein Teil der Funktionen aber gleich ist.

Bei Entwicklern von objektorientierten Sprachen klingelt’ s eventuell schon: Natürlich ist das ein Vererbungspräzedenzfall. Wir könnten also die gemeinsame Funktionalität einfach in Basisklassen auslagern und die plattformspezifischen Implementierungen von einzelnen Funktionen zusätzlich in abgeleiteten Klassen zur Verfügung stellen.
Ein Vorteil wäre die gemeinsame Basis, die nur einmal geschrieben und getestet werden muss. Ein Nachteil natürlich die Vererbungskette, die hier um eine Stufe wächst. Natürlich muss unser Klassendesign insgesamt stimmig sein – die Basisklasse können wir dann beispielsweise als Portable Class Library verpacken oder über „Add as Link“ als Referenz zum jeweiligen Projekt hinzufügen.

Interfaces

Alternativ oder erweiternd dazu bietet sich das Arbeiten mit Interfaces an. Wie wir bereits erfahren haben, gibt es typischerweise einen Bereich des Codes, der plattformübergreifend gültig ist. Einzelne Bereiche sind aber plattformspezifisch zu implementieren. Wenn wir nun unsere wiederverwendbaren Klassen so designen, dass diese nicht mit konkreten Implementierungen arbeiten, sondern lediglich mit Interfaces, so können wir die Implementierungen dieser Interfaces in einer plattformspezifischen Klasse erledigen. Die konkrete plattformspezifische Implementierung wird dann bspw. im Konstruktor der plattformunabhängigen Komponente übergeben. Unterm Strich eine Form von Dependency Injection. Das Arbeiten mit Interfaces hat den Vorteil, dass wir uns ausgiebig mit dem Aufbau unserer Solution beschäftigen müssen und gegebenenfalls eine sehr modulare Lösung erzeugen. Der Nachteil liegt in einer - meinem Empfinden nach - etwas höheren Komplexität.

 

Delegates

Bisweilen funktioniert der Mechanismus, den ich über die Interfaces erreichen kann auch mit Delegates. In diesem Falle ruft mein plattformunabhängiger Code kein Interface auf, sondern eben einen Delegate. Das ist auf den ersten Blick ein bisschen leichtgewichtiger, als die Definition eines Interfaces. Persönlich empfinde ich das Arbeiten mit Interfaces aber als sauberer, da man die Interfaces bereits „von außen“ erkennt, beispielsweise im Architekturtool des Visual Studio (siehe auch oben), während man die Delgaten erst im Code entdeckt. Statt ein Inteface zu implementieren könnten wir so einfach eine Methode übergeben, die unter bestimmten Umständen aufgerufen wird.

Up-/Down-Casting

Um meinen Blog Post hier auch mit praktischer Erfahrung zu belegen, habe ich ein paar kleinere Projekte für Windows 8 und Windows Phone 8 entwickelt, mit dem Vorsatz diese so zu strukturieren, dass ich möglichst wenig plattformspezifischen Code schreiben muss. Ich habe dabei alle hier genannten Mechanismen genutzt, will aber nicht verhehlen, dass ich auch noch ein bisschen mehr verwenden musste.

Als etwas unsauber empfand ich dabei das Up-/Downcasting auf den Typen „Object“. In meiner Portable Class Library habe ich Referenzen auf Objekte gespeichert, die – abhängig davon auf welcher Plattform die Lib zum Einsatz kam – unterschiedlichen Typs waren. Ich musste hier mit einer Referenz arbeiten – der einfachste Weg schien mir das Hinterlegen als „Object“ zu sein. In der jeweiligen Plattform musste ich dann bei Verwendung dieser Referenz entsprechend casten.  Das konnte ich natürlich problemlos tun, da ich ja auf der jeweiligen Plattform weiß, welchen Typ ich erwarte. Richtig sauber fühlt sich das leider nicht an, aber es schien mir eine pragmatische Lösung zu sein. Natürlich empfiehlt es sich hier ein wenig über Exception Handling nachzudenken…

Fazit

Damit bin ich vorerst am Ende meiner Serie über Cross Platform Development zwischen Windows Phone und Windows 8. Die hier genannten Vorgehensweisen könnt Ihr aber ebenso gut auch bei Entwicklung von Serverkomponenten, Desktopsoftware oder sonstiger Windows-Entwicklung heranziehen. Natürlich ist Tauglichkeit und Nutzen immer abhängig vom konkreten Szenario, aber es schadet sicher nicht, sie alle in einem Blogpost gelistet zu finden.

Was ist jetzt mein Fazit? Mein persönliches Fazit ist deutlich besser, als ich erwartet hatte, bevor ich mich mit der Materie befasst habe. Plattformübergreifende Entwicklung mit C# für Windows 8 und Windows Phone 8 ist möglich – wenn man sich mit den Rahmenparametern beschäftigt. Das umfasst unterschiedliche .NET Profile, Unterschiede in der Windows Runtime und im UI sowie im Laufzeitverhalten von Applikationen. Die Logik der Anwendung jedoch ist weitgehend unbeeinflusst – und hier kann man mit den genannten Vorgehensweisen punkten. 

Ist also alles super? Fairerweise muss ich auch hier etwas bremsen: Die Plattformunabhängigkeit bekommt man nicht umsonst. Es wird in jedem Falle etwas Zeit kosten unsere Lösung plattformunabhängig zu gestalten. Ein paar der hier aufgezeigten Vorgehensweisen lohnen aber ohnehin einer näheren Betrachtung, da sie ein paar nette Nebeneffekte Mitbringen – Modularität zum Beispiel. Insofern lohnt sich das Investment vielleicht so oder so.

 

 

Update 21.5.2013:

 

Teil 1

 

Teil 2

 

Teil 3