Tracking-Info
Blog

+49 - 511 - 270 416 0

info@aldebaran.de

Philipp Urner

Modulare Softwareentwicklung
mit PRISM

von Philipp Urner, 24.04.2015 Tags: aldebaran, modulare Softwareentwicklung, PRISM, WPF

Was ist modulare Softwareentwicklung?

In der modularen Softwareentwicklung werden die Aufgaben der Anwendung in voneinander getrennten Modulen gekapselt. Es gibt eine Anwendung, die die Module einbindet und nutzbar macht. Am Beispiel einer Kaffeemaschine könnte man sich das so vorstellen: Ein Modul mahlt die Bohnen und ein Modul erwärmt das Wasser. Die beiden Module können unabhängig voneinander ihre Aufgaben erfüllen und auch unabhängig voneinander entwickelt und getestet werden. Wenn die Kaffeemaschine später auch noch Milch aufschäumen soll, kann dies ebenfalls unabhängig von den anderen Funktionen umgesetzt werden.

Wieso modulare Softwareentwicklung?

Bei der Planung eines größeren Projektes hat sich immer mehr heraus kristallisiert, dass die Anwendung teils recht unterschiedliche Anforderungen erfüllen soll. Hinzu kam, dass die Anwendung so schnell wie möglich zum Einsatz kommen und dann kontinuierlich erweitert werden soll. Aus diesen Gründen hat sich eine modulare Umsetzung fast schon aufgedrängt.

Die Anwendung soll in C# und WPF (Windows Presentation Foundation) umgesetzt werden.

Mit einer monolithisch aufgebauten Softwarelösung wäre die Umsetzung mit einem höheren Aufwand - vor allem bei den Tests - verbunden gewesen, und eine schnelle Inbetriebnahme hätte sich schwierig gestaltet.

Der modulare Aufbau hat hier also mehrere Vorteile:

  • Separate Entwicklung der unterschiedlichen Anforderungen
  • Separates Tests der unterschiedlichen Module
  • Frühe Nutzung der Anwendung und stetige Erweiterung der Features ohne Änderungsnotwendigkeit für den existierenden Quellcode

PRISM

Bei der Recherche nach einem Framework, welches uns bei diesen Anforderungen unterstützen kann, sind wir auf PRISM (PResentation Integration SysteMs) for WPF gestoßen. Nein - dieses PRISM hat nichts mit der gleichnamigen Überwachungs-Software zu tun!

Das Framework bietet einen sehr guten Weg, eine modulare Anwendung zu entwickeln, indem es die benötigte Logik zum Einbinden der Module liefert. Dies geht so weit, dass Regionen in der Oberfläche unterstützt werden. Regionen werden in der Hauptansicht definiert und jede View in einem Modul kann festlegen, in welcher dieser Regionen sie angezeigt werden soll.

Für unseren Kunden sollte eine Anwendung geschaffen werden, über die Hardware administriert und über die u. a. Einstellungen in Form von Parametern ausgelesen, eingespielt und verglichen werden können. Da diese Anforderungen unterschiedliche Oberflächen und Logik erfordern, haben wir sie in getrennten Modulen umgesetzt.

Wir haben dafür die Oberfläche in zwei Regionen unterteilt. In Region 1 werden die Module zur Arbeit mit den Parametern angezeigt. Jedes Modul bekommt automatisch einen eigenen Tab zugewiesen; die Logik zum Erzeugen der Tabs übernimmt PRISM für uns. In Region 2 wird ein weiteres Modul angezeigt, in dem Logmeldungen visualisiert werden.

Prism Regions
Skizze der Oberfläche mit Regionen

Die Definition der Regionen ist in der WPF-Anwendung im XAML-Code des Hauptfensters hinterlegt und wird an einer zentralen Stelle gepflegt.

Um eine Region zu definieren, muss lediglich der 'RegionName' festgelegt werden. Am Beispiel von Region 2 könnte das wie folgt aussehen:

Quelltext:  Alles auswählen  |  Zeilennummerierung an/aus
  1. <ItemsControl Prism:RegionManager.RegionName="BottomRegion" />

Zusätzlich muss innerhalb des Moduls definiert werden, in welcher Region es angezeigt werden soll.

Hierfür reicht es, wenn es innerhalb der dll, in der das Modul definiert ist, eine Implementation des Interfaces 'IModule' aus dem 'Microsoft.Practices.Prism.Modularity Namespace' angelegt wird und in der 'IRegionViewRegistry Instance' die Region zugewiesen wird.

Quelltext:  Alles auswählen  |  Zeilennummerierung an/aus
  1.     public class MyModule : IModule
  2.     {
  3.         private readonly IRegionViewRegistry _regionViewRegistry;
  4.  
  5.         public MyModule(IRegionViewRegistry registry, IUnityContainer container)
  6.         {
  7.             _regionViewRegistry = registry;
  8.         }
  9.  
  10.         public void Initialize()
  11.         {
  12.             _regionViewRegistry.RegisterViewWithRegion("BottomRegion", typeof(MyView));
  13.         }
  14.     }
  15.  

Der Vorteil dieser Lösung ist, dass die Zugehörigkeit zu einer Region in den Modulen definiert wird. Dadurch muss die eigentliche WPF-Anwendung die Module nicht kennen oder referenzieren. Es ist denkbar, dass verschiedene Anwender die gleiche Anwendung mit unterschiedlichen Modulen installieren und somit der Funktionsumfang beeinflusst werden kann - ohne Anpassungen am Quellcode.

Aber was ist, wenn es doch mal nötig wird, dass die Module miteinander kommunizieren?

Ein Anwendungsbeispiel könnte sein, dass es ein Modul gibt, das Logmeldungen der anderen Module sammelt und dem Benutzer anzeigt. Da die Module sich untereinander nicht kennen, gestaltet sich dies auf den ersten Blick als schwierig. Aber auch hier liefert PRISM eine gute Lösung: den 'IEventAggregator'. Über dieses Interface können Nachrichten zwischen den Modulen und der WPF-Anwendung ausgetauscht werden. Eine Nachricht kann z.B. in einer dll, die von allen Modulen verwendet wird, definiert werden. Die Klasse muss von 'CompositePresentationEvent' abgeleitet werden, und es wird der Nachrichten-Typ angegeben. In unserem Fall reicht ein String aus:

Quelltext:  Alles auswählen  |  Zeilennummerierung an/aus
  1.     public class LogEvent : CompositePresentationEvent<string>
  2.     {
  3.     }

An der Stelle, an der die Nachricht verschickt werden soll, wird über den 'IEventAggregator' auf das 'LogEvent' zugegriffen und die 'Methode Publish' aufgerufen.

Quelltext:  Alles auswählen  |  Zeilennummerierung an/aus
  1. EventAggregator.GetEvent<LogEvent>().Publish("Meine Logmeldung");
  2.  

Die Klasse, die die Nachricht verarbeiten soll, muss dann nur noch definieren, wie vorzugehen ist, wenn ein Nachricht dieses Typs ausgelöst worden ist.

Quelltext:  Alles auswählen  |  Zeilennummerierung an/aus
  1. EventAggregator.GetEvent<LogEvent>().Subscribe(OnNewLogEvent);
  2.  
  3. private void OnNewLogEvent(string message)
  4. {
  5.    …
  6. }

Des Weiteren bietet das PRISM Framework weitere Unterstützung wie 'Dependency Injection'. Hier werden mehrere Dependency Injection Frameworks unterstützt. Wir haben uns für 'Unity' entschieden und sind damit sehr gut gefahren.

Fazit

Wer sich mit dem Thema modulare Softwareentwicklung näher beschäftigen möchte, kommt meiner Meinung nach nicht an PRISM vorbei. Zumindest einen Blick sollte man riskieren. Nach einer überschaubaren Einarbeitungszeit können relativ schnell erste Ergebnisse erzielt werden. Ein weiterer Pluspunkt ist, dass mittlerweile sehr viele Beispiele im Internet vorhanden sind.