15 March 2010 DI, HowTo, IoC Robert Muehsig

image Vor gut zwei Wochen hat Alexander Groß bei der .NET Usergroup Dresden das Thema Dependency Injection in den Raum geworfen und es auch sehr anschaulich an Beispielen verdeutlicht. Er zeigte den IoC Container Castle Windsor und warum ein ServiceLocator keine so gute Idee ist. Mit dem Blogpost versuche ich mal diese scheinbare Raketenwissenschaft etwas aufzuschlüsseln.

Dependency Injection? Service Locator? WTF?

Wenn man über das Thema sich etwas beließt, dann kommt man früher oder später zu den riesigen Erklärungen von Martin Fowler (ServiceLocator vs. DI). Das ist bestimmt toll beschrieben und ich hab das schon x-mal angefangen, aber bis zu dem .NET Usergroup-Treffen war es mir noch nicht ganz bewusst wie das praktisch denn nun aussieht. Darum versuch ich es mal ganz einfach.

Worum geht es hier eigentlich?

Jede Software hat irgendwelche "Abhängigkeiten". Sei es eine Datenbank, sei es das Filesystem auf dem zugegriffen wird oder sei es ein interner Dienst oder Service der genutzt wird. Damit man das komplizierte Gebilde später auch vielleicht irgendwie testen kann, empfiehlt es sich auf eine Schnittstelle zu programmieren.
Kleines Szenario: Wir haben ein Formular wo sich User anmelden können. Nach dem anmelden soll eine Mail verschickt werden. Die Daten validieren und verarbeiten macht der "UserService". Dieser benutzt zum Mails versenden etwas vom Typ "IMailService".
Jetzt kommt aber genau der interessante Punkt: Woher soll unser UserService wissen, woher er eine Instanz vom Typ "IMailService" bekommen soll?

Variante 1: Service Locator

Achtung: Jetzt folgt Code, wo der eine oder andere mit dem Kopf schüttelt -  der Code ist heute noch so im Einsatz ;)

Die erste Idee wie man solche Abhängigkeiten "kontrollieren" kann wäre wenn man eine Art "Gott-Klasse"/"Setup" hat, in dem man die Komponenten registriert. Mit dieser "Gott-Klasse" könnten wir uns auf Befehl jeden Typen erstellen:

    public class InstanceFactory : IInstanceFactory
    {
        public T GetInstanceOf<T>()
        {
            if (typeof(T) == typeof(IMailService))
            {
                return (T)(object)new EmailService();
            }
        }
    }

An der Stelle könnte man natürlich auch auf eine XML Datei oder irgendwas anderes zugreifen.

Angewendet würde diese Klasse im UserService dann so:

IInstanceFactory factory = new InstanceFactory();
IMailService srv = factory.GetInstanceOf<IMailService>();

Diese "Gott-Klasse" könnte natürlich auch eine statische Klasse sein, aber um den UserService testbar zu halten, können wir über den Konstruktor im UnitTest ein andere InstanceFactory reingeben:

        public UserService()
            : this(new InstanceFactory())
        {
        }

         public UserService(IInstanceFactory factory)
        {
            this._factory = factory;
        }

Im Normalfall wird also unsere Implementation von oben genommen und ansonsten könnten wir auch eine TestInstanceFactory reingeben. Geht soweit auch, ist aber eigentlich nicht so gut wie ich feststellen musste.

Wo liegt beim Service Locator das Problem?

Bei dieser Variante weiß man nicht genau, welche Abhängigkeiten eine Klasse hat. Wenn man die Abhängigkeiten im Konstruktor sieht, dann weiß man, dass z.B. der UserService eine Instanz vom Typ IMailService benötigt. Durch diese "Gott-Klasse" kann man plötzlich kreuz und quer irgendwelche Services aufrufen. Das macht das debugging und testen schwieriger.

Die bessere Methode: Inversion of Control Container

Hier gilt (wie immer) : Solides Halbwissen ist vorhanden - wenn ich hier Quatsch erzähle, dann berichtigt mich ruhig.

Um nicht zu weit auszuschweifen: Um die Abhängigkeiten auf den ersten Blick zu erkennen, ist es meiner Meinung nach gut, wenn diese über den Konstruktor definiert werden. Eine andere Art wäre dies über Properties zu machen. Das ist aber IMHO nicht so einleuchtend wie im Konstruktor.

Rund um das Inversion of Control Thema gibt es bereits eine Vielzahl von Frameworks, die einem dabei helfen:

Auf den ersten Blick ähneln sich die Konzepte vom Service Locator und diesen Frameworks. Bei Den IoC Container legt man immer eine Art "Konfiguration" nach dem Schema: "Wenn du nach Instanz von Typ X gefragt wirst, dann gibt ihm eine Instanz vom Typ X." an. Allerdings sind die benannten Frameworks hier weitaus cleverer als mein Code von oben.

Wir spinnen das Szenario von oben weiter: Der UserService hat auch ein Interface IUserService und wird im UserController verwendet. Hier mal ein Beispiel mit Castle.Windsor:

   WindsorContainer container = new WindsorContainer(new XmlInterpreter());
   IUserService srv = container.Resolve<IUserService>();

Die "Resolve" Methode ist im Prinzip ähnlich wie das "GetInstanceOf" Methode. Allerdings hat unser UserService wiederrum eine Abhängigkeit auf den EmailService. Das wird allerdings alles vom Framework geregelt und alle Abhängigkeiten werden sauber aufgelöst.

Der Vorteil an der Methode ist, dass man im Unittest einfach so die Klassen benutzen kann und sofort die Abhängigkeiten durch den Konstruktor sieht. Das macht die Sache wesentlich durchschaubarer.

Ich erspare mir hier mal ein konkretes Beispiel, da es im Netz sehr viele gute HowTos zu den einzelnen Frameworks gibt.

Diese Frameworks können natürlich noch wesentlich mehr als pure Instanzen zurückgegeben, aber das würde jetzt zu weit führen. Ich hoffe ich konnte erstmal ein wenig Licht ins Dunkel bringen. Falls ich in diesem Post irgendwelche Sachen aber komplett falsch verstanden habe oder einfach Begrifflichkeiten verwechselt habe, dann korrigiert mich bitte :)


Written by Robert Muehsig

Software Developer - from Saxony, Germany - working on primedocs.io. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de