Design Patterns: Teil 2 – Dependency Injection mit Castle Windsor

In Teil 1 der Reihe haben wir die Ideen hinter den Prinzipien “Inversion-of-Control” und “Dependency Injection” kennen gelernt. Die Anwendung dieser Techniken trägt sehr viel zu einer modularen und leicht wartbaren und erweiterbaren Software-Architektur bei.

Im 2. Teil beschäftigen wir uns mit der zentralen Komponente (Container), die Instanzen erzeugt und alle Abhängigkeiten automatisch zur Laufzeit erkannt und erfüllt. Zur Erinnerung hier nochmals die Abbildung aus dem ersten Artikel.

44

Viele Frameworks existieren, die die Funktionalität dieser “Container” Komponente umsetzen. In diesem Artikel werde ich denCastle Windsor Containerverwenden.

Um das LogManager Beispiel sinvoll zu erweitern, wollen wir eine GUI (hier WinForms) hinzufügen. Jedes Form soll protokolieren wenn es geladen wurde. Anstatt das in jedem konkreten Form zu machen, erzeugen wir eine Basisklasse von der unsere Forms im Endeffekt ableiten –> KISS & DRY !!. Hier führen wir einen neuen Konstruktor für ein, der als Parameter eine Instanz von LogManager übergibt.

    1: public class BaseForm : Form
    2: {
    3:     private readonly LogManager _logManager;
    4:
    5:     public BaseForm() {}
    6:
    7:     public BaseForm(LogManager logManager)
    8:     {
    9:         _logManager = logManager;
   10:     }
   11:
   12:     protected override void OnLoad(EventArgs e)
   13:     {
   14:         base.OnLoad(e);
   15:         _logManager.WriteToLog(string.Format("Form {0} loaded", Name));
   16:     }
   17: }

Wir haben jetzt zusätzlich zu der Abhängigkeit LogManager –> ILogWriter noch die Abhängigkeit BaseForm –> LogManager. Würden wir ohne Hilfe eines IoC Containers (Windsor Container) arbeiten, müssten wir zuerst händisch eine Instanz einer ILogWriter Implementierung erzeugen, danach einen LogManager und dann könnten wir eine Instanz von BaseForm erzeugen. Hierbei würde wieder jede Flexibiltät verloren gehen, da wir zur Entwicklungszeit im Code angeben müssten, welche konkreten Implementierungen verwendet werden.

Deshalb lassen wir die Arbeit den Castle Windsor Container erledigen…zur Laufzeit!

Schritt 1: Download des Frameworks

(Microkernel/Windsor ist das was wir aus der Liste brauchen)

Schritt 2: Referenzieren der Assemblies

In der Projektmappe mit Rechtsklick “Add Reference” folgende Assemblies referenzieren.

54

Schritt 3: Konfiguration des Containers, registrieren der Komponenten

Alle Klassen müssen zuerst in unserem Container als Komponenten registriert werden, damit Instanzen und Abhängigkeiten erzeugt bzw. aufgelöst werden können. Im einfachsten Fall geschiet das direkt im Code. Hier habe ich eine ApplicationServives Klasse als Singleton implementiert (Statische Klasse), die beim Start der Anwendung einmal die Methode InitializeContainer aufruft.

    1: public static class Application
    2: {
    3:     private static IWindsorContainer _container;
    4:
    5:     static void InitializeContainer()
    6:     {
    7:         _container = new WindsorContainer();
    8:
    9:         _container.AddComponent("LogManager", typeof(LogManager));
   10:         _container.AddComponent("ILogWriter", typeof(ILogWriter), typeof(FileLogWriter));
   11:         _container.AddComponent("BasePage", typeof(BasePage));
   12:     }
   13: }
   14:
   15: public static T GetComponent<T>()
   16: {
   17:     return (T)_container.Resolve(typeof(T));
   18: }

Die Methode registriert die benötigten Komponenten im Container. Interessant ist dabei Zeile 10, in der ILogWriter registriert wird. Genau hier wird umgesetzt worüber im 1. Teil geschrieben wurde. Es wird das Interface ILogWriter registriert und im 3. Paramater wird EINE KONKRETE Implementierung von ILogWriter angegeben, die verwendet werden soll. Man muss sich die Interfaces als Services vorstellen, die gewisse Operationen anbeiten (zB WriteToLog) und die konkreten Klassen als Komponenten welche diese Services umsetzen. Wird das Service ILogWriter angefordert (siehe nächstes Code Snippet), wie im Konstruktor von LogManager der Fall ist, instanziiert der Container die Klasse FileLogWriter.

    1: ILogWriter logWriter = ApplicationServices.GetComponent<ILogWriter>();

Wird das Ganze so wie hier im Code gemacht, gewinnt man noch nicht viel Flexibiltät. Was man gewinnt ist ein zentraler Ort an dem man die Komponenten verwalten kann! Denkbar ist zum Beispiel die Inititalize Methode in einer eigenen Assembly zu definieren und pro System (Test / Produktion/ ..) eine Initialisierungs-Assembly zu verwenden. Es reicht, für die Initialize Methode der Test- Assembly, eine Zeile zu tauschen und man arbeitet mit dem Service ILogWriter gegen eine Mock-Implementierung.

    1: ..
    2: _container.AddComponent("ILogWriter", typeof(ILogWriter), typeof(MockLogWriter));
    3: ..

Noch flexibler ist es XML Konfigurationsdateien zu verwenden. Dabei hat man die Wahl zwischen einer “eigenständigen” XML-Datei oder man fügt der app.config (oder web.config) eine entsprechende Sektion hinzu. In unserem Fall sähe die app.config folgendermassen aus:

    1: <?xml version="1.0" encoding="utf-8" ?>
    2: <configuration>
    3:     <configSections>
    4:       <section
    5:           name="castle"
    6:           type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
    7:     </configSections>
    8:   <castle>
    9:     <components>
   10:       <component
   11:           id="LogManager"
   12:           type="ContainerDemo.LogManager, ContainerDemo" />
   13:       <component
   14:           id="ILogWriter"
   15:           service="ContainerDemo.ILogWriter, ContainerDemo"
   16:           type="ContainerDemo.FileLogWriter, ContainerDemo" />
   17:       <component
   18:           id="BasePage"
   19:           service="ContainerDemo.BasePage, ContainerDemo" />
   20:     </components>
   21:   </castle>
   22: </configuration>

Die Initialiserung ist analog zu oben, jedoch muss beim Erzeugen der Container Instanz noch ein Interpreter für XML mitgegeben werde, der Castle anweist in der Sektion “castle” der app.config die Konfiguration zu suchen.

    1: _container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));

Soweit zu einem kurzen Überblick über Dependency Injection und Inversion-of-Control mit dem Castle Windsor Container. Die Möglichkeiten gehen weit über das hinaus was hier möglich ist zu zeigen. Viele Beispiele und die Dokumentation zu Castle Windsor finden sich in den Links unten.

Links

Castle Windsor Dokumentation

Weitere Frameworks

Spring.NET Liste von IoC Frameworks von Scott Hanselmann