Laden...

Software Patterns: Inversion of Control & Dependency Injection - Grundlegende Fragen

Erstellt von JMano vor 7 Jahren Letzter Beitrag vor 7 Jahren 7.639 Views
J
JMano Themenstarter:in
38 Beiträge seit 2013
vor 7 Jahren
Software Patterns: Inversion of Control & Dependency Injection - Grundlegende Fragen

hi Zusammen

Ich habe begonnen mich in einige Software Pattern ein zu lesen,
und habe bei einigen noch Verständnis Probleme,
bei denen ich hoffe hier bei einer Diskussion vl mehr Verständnis dafür zu entwickeln.

Zunächst mal gibt es Patterns die sehr einfach sind, und die ich auch hin und wieder (auch unbewusst, ohne Wissen ihrer Existenz) benutzt habe.
Aber es gibt eben auch weit größere/komplexere Patterns, bei denen ich mir immer denke,
wie komme ich den da vorher drauf die zu verwenden (wo ich mir noch dazu eine Anpassung überlegen muss, weil 1:1 geht ja eh nur in Beispielen).

Egal, jetzt mal aber Konkret zu Inversion of Control & Dependency Injection.
Das ist grad das Thema, das in meinem Kopf als „nicht ausreichend verstanden“ herumfliegt.
(Als Hinweis, ich nutze gerade meist C# oder Java)

Dabei geht es ja um mehrere Punkte/Ideen.
Man verwendet Interfaces/Abstractionen um nicht mehr von konkreten Implementierungen ab zu hängen.
Das ist eigentlich auch klar und einleuchtend, kommt aber mit so ein paar harken für mich mit …
Wenn ich also eine Methode aufrufe, und Parameter mitgebe, und dabei Interfaces erwartet werden ist es von Vorteil da ich da beliebige Implementierungen einfüllen kann.

Bei einfachen (jetzt nicht gleich ganz die primitiven) Daten Parametern stelle ich mir das ja noch toll vor. aber wenn es dann um Objekte geht die viel Logic oder so mitbringen wie irgend ein service oder so … naja
Allerdings soll es ja auch so sein, damit ich in der Methode mich auch nicht darum kümmern muss wie das gerade geht
(Ich weiß nur, dass das erhaltene Object vom typ IDataOutputController genutzt werden kann um die daten aus zu geben)

Seis drum.

Aber wenn ich jetzt also „beliebige“ Objekte die die Interfaces implementieren nutzten kann muss ich das ja auch irgendwo entscheiden:
Welche das konkret sind (1), und sie auch irgendwo instanziieren (2), und irgendwie auch zu den Stellen im Programmablauf bringen wo sie genutzt werden (3).
Und bei den Problemen (1, 2, 3) ist dann die Frage wo/wie ich das mache.

1: passiert sowas nur zu compile time oder ist dafür auch runtime gedacht?

3 & 2: Wenn ich die irgendwo am „Beginn“ des Programm Laufs oder Zentral habe, muss ich diese Objekte ja immer mit durchschleifen, was ja alles unnötig kompliziert macht.

2 & 3: Wenn ich das instanziieren dann nur „vor Ort“ mache, ist das dann wieder über den Code Überall verstreu und bei Änderungen muss ich überall hineingreifen.
Es gibt da zwar auch andere Mechanismen (die ich auch nicht ganz versteh) die dann per Annotation (oder andem) funktionieren.
Aber dabei muss ich dann doch auch irgendwie Zentrall die Verknüpfung von Interface auf Implementierung herstellen.
Und das Widerspricht dann wieder dem Prinzip keine Gott-Klassen haben zu wollen. (Oder auch in XML ausgelagert zu haben)

Wie kommt man da also um diese Knoten drum rum?

656 Beiträge seit 2008
vor 7 Jahren

Die beiden gehen meist Hand-in-Hand, weil das holen einer Abhängigkeit per Konstruktor oder Property (Dependency Injection, weil man die Abhängigkeit nicht selbst innerhalb per new erzeugt, sondern von außen herein bekommt) gleichzeitig auch bedeutet, dass man nicht mehr selbst die Kontrolle drüber hat, was man da tatsächlich bekommt (Inversion of Control, der Aufrufende entscheidet, ob mein IDocumentRepository ein Fake im Unit-Test, ein Dateibasiertes FS-Repository oder ein Datenbankbasiertes DB-Repository in Production ist).

Oft werden dazu Libraries verwendet (zb. Castle Windsor, TinyIoC, Microsoft Unity und noch viele mehr), aber man kann natürlich auch per Hand rand - und dann passiert genau das, was du grade ansprichst: du musst an einer Stelle (im Zweifelsfall ganz oben, also in Program.Main oder so) alles erzeugen und bis ganz unten runtergeben - oder zumindest irgendwo zwischenspeichern, bis du die jeweiligen Instanzen brauchst).
Hält dich natürlich nicht davon ab, es per Hand zu machen - wie es oft in Unit-Tests uä. gemacht wird (weil nur ein kleiner Teil des Gesamtsystems geprüft werden soll).

Ob deine Dependencies dann Interfaces, abstrakte Klassen oder einfach nur Klassen mit ggf. unterschiedlichen Property-Werten/Konfigurationen sind, hängt von der Komplexität, Flexibilität und Notwendigkeit bei deiner jeweiligen Implementierung ab; und musst du fallweise entscheiden - oder zb. per Schema-F sagen, dass du zwecks Flexibilität immer auf Interfaces setzt.

Kleines Beispiel:

class LineBasedProcessor
{
    public void ProcessLines(string[] lines)
    {
        Console.WriteLine($"Processing {lines.Length} lines");
    }
}

Hier hast du eine fixe Abhängigkeit zu Console, und du bist darauf limitiert nur auf die Konsole zu schreiben.

class LineBasedProcessor
{
    public void ProcessLines(IOutputWriter writer, string[] lines)
    {
        writer.WriteLine($"Processing {lines.Length} lines");
    }
}

Jetzt schreibst du immer noch, aber es ist außerhalb deiner Kontrolle, wo die Ausgabe hingeht - oder ob sie überhaupt irgendwo landet (IoC). Damit hast du jetzt eine Abhängigkeit zu einem IOutputWriter, die von jemand anders bereitgestellt werden muss (DI).

D
985 Beiträge seit 2014
vor 7 Jahren

Es ist natürlich sinnvoller diese Abhängigkeit über den Konstrutor zu regeln, dann bleibt der Aufruf so wie vorher und dort, wo ich mit der Instanz arbeite muss ich nichts von dem IOutputWriter wissen.


class LineBasedProcessor
{
    private readonly IOutputWriter writer;
    public LineBasedProcessor( IOutputWriter weriter )
    {
        this.writer = writer;
    }
    public void ProcessLines(string[] lines)
    {
        writer.WriteLine($"Processing {lines.Length} lines");
    }
}

J
JMano Themenstarter:in
38 Beiträge seit 2013
vor 7 Jahren

Hi, danke für die antworten!
Das ist auch im Moment so mein Verständnis von IoC & DI.

Für die Injektion der Dependency gibt es ja scheinbar verschiedene Methoden (Konstruktoren, property, .... )

Aber nach wie vor muss irgendwo eine Auflösung Interface zu Implementation stattfinden. Das muss auch in den Frameworks passieren ... das ist das was ich nicht nachvollziehen kann wann/wo das so funktioniert, dass es nicht zu einer Gott-Klasse (alles in einem Platz was man auch nicht will), klappt. (Oder soll man diesen Hintergedanken einfach ignorieren?)

16.827 Beiträge seit 2008
vor 7 Jahren

Eine Gott-Klasse gibt es in 99% der Fälle im Prinzip schon, denn die besagt, welche Implementierung hinter welchem Interface steckt.
Bei Unity sind das zB. die UnityContainer, bei ASP.NET Core die Startup.cs

Bei ASP.NET sieht es so aus, dass man i.d.R. ein Framework braucht / in 99,9% der Fälle nutzt. Das liegt an dem Middleware-Prinzip wie Requests bearbeitet werden.
Bei Konsolenanwendungen kannst Du auch auf solch ein Framework verzichten und im Prinzip beim Start der Konsolenanwendung die Implementierungsklassen festlegen.
Frameworks geben aber einem einen festen Rahmen, an dem man sich orientieren kann. Daher ist das sehr praktisch und aus disziplinarischer sicht schon gut so.

Wichtig ist der Grundgedanke:
Du darfst in den einzelnen Klassen immer nur gegen ein Interface arbeiten; Du darfst hier die Implementierung nicht kennen - musst Du auch nicht.
Ob nun die Referenz via Konstruktor oder wie Methode kommt: im Prinzip egal. Der Konstruktor bietet sich meistens an. Properties würde ich wirklich nur als letzte Variante nehmen, denn da ist weder am Konstruktor noch an der Methode ersichtlich, was die Methode braucht.
Da knallst dann gerne mal mit einer NullReferenceException, weil nicht beschrieben wurde, wann welche Property bei welcher Methodennutzung gesetzt werden muss.

Nur Interfaces lassen sich auch mocken und stellen so erst die Basis für sorgfältige Softwaretests / Unit Tests dar.

W
872 Beiträge seit 2005
vor 7 Jahren

Dependencies sollte man möglichst in den Konstruktor nehmen, damit man nicht mit einer unvollständigen/inkonstistenten Instanz arbeiten kann. Insbesondere wenn man Code lange nicht mehr angefasst hat, passiert so etwas sehr leicht.

D
985 Beiträge seit 2014
vor 7 Jahren

Wenn DI via Property, dann sollte man mit der Null-Strategy arbeiten. Das hält den Code schlank und robust.

Um beim Beispiel zu bleiben


class LineBasedProcessor
{
    private static readonly IOutputWriter nullwriter = new NullOutputWriter();
    private IOutputWriter writer;

    public void ProcessLines(string[] lines)
    {
        // Zugriff auf den Writer erfolgt jetzt über die Property!
        Writer.WriteLine($"Processing {lines.Length} lines");
    }

    public IOutputWriter Writer
    {
        get { return writer ?? nullwriter; }
        set { writer = value; }
    }
}

interface IOutputWriter
{
    void WriteLine( string text );
}

class NullOutputWriter : IOutputWriter
{
    public void WriteLine( string text )
    { }
}

16.827 Beiträge seit 2008
vor 7 Jahren

Damit wird leider wieder eine enge Kopplung zwischen Interface und Implementierung über Dritte hergestellt - das würde ich vermeiden.
Wenn es wirklich notwendig ist mit Property Injection zu arbeiten - was es in 99,99999% Fällen nicht ist, dann lieber mit Code Contracts arbeiten.
Dann bekommt man zur Programmierzeit bei der Methode ein Warning, wenn notwendige, interne Properties nicht gesetzt wurden.

D
985 Beiträge seit 2014
vor 7 Jahren

Damit wird leider wieder eine enge Kopplung zwischen Interface und Implementierung über Dritte hergestellt - das würde ich vermeiden.

Das kann ich jetzt nicht nachvollziehen.

Wer auch immer die Implementierung des Interfaces vornimmt und davon eine Instanz übergibt kann ich in dieser Klasse nicht nachhalten - egal ob per Konstruktor oder Property übergeben.

Oder ist es, weil die implementierende Klasse ausserhalb liegt? Dann einfach die implementierende Klasse als private Klasse in die Klasse hinein und gut.

DI per Property ist definitiv ein Exot und sollte es auch bleiben. Aber wenn, dann so und auch nur für Abhängigkeiten, die nicht zwingend benötigt werden (die per Konstruktor)

16.827 Beiträge seit 2008
vor 7 Jahren

Die Implementierungen stecken aber i.d.R. in verschiedenen Assemblies.
LineBasedProcessor in Assembly A
NullOutputWriter in Assembly B.

In diesem Beispiel von Dir müsste Assembly A auch Assembly B kennen.
Für mich eine vollkommen unnötige Referenz und damit harte Kopplung.

D
985 Beiträge seit 2014
vor 7 Jahren

Da die Klasse LineBasedProcessor die Null-Strategy für den Writer benötigt ist es ein Implementierungsdetail der Klasse selber. Somit gehört die NullOutputWriter Klasse ganz nah an diese Klasse (eigentlich als private Klasse in der LineBasedProcessor Klasse).

P
1.090 Beiträge seit 2011
vor 7 Jahren

Das Problem ist, das NullOutputWriter einfach nichts macht. Du bekommst, keine Exception oder Log Eintrag.

Ein ehemaliger Arbeitskollege hat so Programmiert, alles und jedes auf Null geprüft und wenn es Null war einfach nichts gemacht, noch nicht mal einen Log Eintrag.

Wenn jetzt ein Kunde anruft und sagt Funktion XYZ macht nichts, hab ich schon böseVorahnungen. Wenn ich mir dann noch die Logs anschaue und keine Fehler finde. Hab ich echt schlechte Laune. Dann darf ich von den Funktionsaufruf an, den Code durchgehen (um Log Eintrage erweitern) und Raten woran es jetzt liegen könnte. und wenn ich dann bei mir den Fehler nicht finde. Die Dll beim Kunden ersetzen und schauen woran es liegen könnte.

Quellcode der keine Fehler schmeißt und im Zweifel einfach nichts macht, ist nicht Robust, der ist nur schwer zu Warten.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

D
985 Beiträge seit 2014
vor 7 Jahren

Das NullOutputWriter nichts macht ist kein Problem sondern der Kern der Null-Strategy.

Und wenn etwas zwingend benötigt wird, dann ist die Null-Strategy genauso falsch wie das DI per Property.

Die Null-Strategy macht nur Sinn wenn es sich um optionale Dependencies handelt.

16.827 Beiträge seit 2008
vor 7 Jahren

Also ich find kein Gefallen an dieser Null Strategy; sehe irgendwie auch keinerlei Vorteile von einer Klasse, deren Reaktion nicht nachvollziehbar ist.
Und wenn man den Code nur von Außen sieht, dann kann man das nicht nachvollziehen. Optional hin oder her.

D
985 Beiträge seit 2014
vor 7 Jahren

Es muss einem nicht gefallen und trotzdem gibt es diese Null-Strategy und unbewusst werkelt diese an der ein oder anderen Stelle im Hintergrund im .Net Framework mit(*) und man verwendet diese ohne es zu wissen, weil man ein Implementierungsdetail auch nicht wissen muss.

(*) z.B. Task-Library: Erzeugt man einen Task und gibt kein CancellationToken mit, so erfolgt intern der effektive Aufruf trotzdem mit einem CancellationToken.None (das tut aber nix, ist einfach nur da). Auch das ist Null-Strategy (also was soll passieren, wenn keine Informationen vorliegen)

Und bevor man mich falsch versteht:

Es geht nicht darum, dass jetzt jeder seinen Code mit Null-Strategy vollpflastern soll. Genauso wenig wie man überall eine StateMachine, Observer, oder wer weiß was für ein Pattern blind hinklatscht in der Hoffnung es wird schon funktionieren.

Es ist auch nicht die allumfassende Lösung für jedes Problem.

Jedes Pattern hat seinen Sinn und Zweck. Ob und wie man es verwendet oder nicht hängt davon ab, was man wie erreichen möchte. Pauschal kann man dazu nichts sagen.

16.827 Beiträge seit 2008
vor 7 Jahren

Der Vergleich hinkt, denn der CancellationToken ist ein optionaler Parameter der asynchronen Methoden.
Damit ist ersichtlich, dass man etwas übergeben kann und ansonsten eben eine Alternative erfolgt.

Bei Deiner Nullstrategie via Klasseneigenschaften erkenne ich das von Außen der Methode nicht an.
Wenn Deine Nullstrategie mit Hilfe von optionalen Parametern erfolgt; klar, das ist für mich auch nachvollziehbar.
Aber von Außen nicht nachvollziehbarer Code (wie der um 08:54) geht für mich gar nicht.

public class Klasse
{
   static readonly IOutputWriter nullwriter = new NullOutputWriter();

   public IOutputWriter Writer
    {
        get { return writer ?? nullwriter; }
        set { writer = value; }
    }

   public void ProcessLines(string[] lines)
    {
        // Zugriff auf den Writer erfolgt jetzt über die Property!
        Writer.WriteLine($"Processing {lines.Length} lines");
    }
}

ist was völlig anderes als

public class Klasse
{
   static readonly IOutputWriter defaultWriter = new NullOutputWriter();

   public void ProcessLines(string[] lines, IOutputWriter optionalWriter= null)
    {
        (optionalWriter ?? defaultWriter).WriteLine($"Processing {lines.Length} lines");
    }
}
J
JMano Themenstarter:in
38 Beiträge seit 2013
vor 7 Jahren

Eine Gott-Klasse gibt es in 99% der Fälle im Prinzip schon, denn die besagt, welche Implementierung hinter welchem Interface steckt.
...
Wichtig ist der Grundgedanke:
Du darfst in den einzelnen Klassen immer nur gegen ein Interface arbeiten; Du darfst hier die Implementierung nicht kennen - musst Du auch nicht.
...
Nur Interfaces lassen sich auch mocken und stellen so erst die Basis für sorgfältige Softwaretests / Unit Tests dar.

Danke, für die Bestätigung meines Verständnisses!

Eines verstehe ich noch nicht ganz.

Bei z.B.: WindsorCastl:
https://github.com/castleproject/Windsor/blob/master/docs/README.md


var container = new WindsorContainer();

// adds and configures all components using WindsorInstallers from executing assembly
container.Install(FromAssembly.This());

// instantiate and configure root component and all its dependencies and their dependencies and...
var king = container.Resolve<IKing>();
king.RuleTheCastle();

Hier brauche Ich ja überall wo ich ein Interface zu einer Implementierung auflösen will eine Instanz des Containers.
Wenn ich den nur einmal im Programm haben will, muss ich den ja entweder:
statisch irgendwo ablegen () oder
ihn überall hin durchschleifen (hatten wir doch schon) oder
ich erselle ihn überall neu (überall boilerplate + abhängig davon)
daher sehe ich nicht wie ich das verbessere.

Selbige Frage stellt sich bei mir auch für Java und Guice (von google):
https://github.com/google/guice/wiki/Motivation

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new BillingModule());
    BillingService billingService = injector.getInstance(BillingService.class);
    ...
  }

Wenn ich sowas in gröseren Projekten einsetzt denke ich auch das es paar sachen besser macht (IoC) aber dadurch andere Porbleme bringt.

16.827 Beiträge seit 2008
vor 7 Jahren

I.d.R. erfolgt die Auflösung via Service Locator Pattern.
Je nachdem mit welcher Technologie Du arbeitest, gibt es da aber automatismen, zB in ASP.NET mit einem Dependency Resolver.

Ich seh ehrlich gesagt kaum bis nichts an Probleme, die dadurch auftreten.
Allein der Gewinn (lose Kopplung, Testbarkeit..) ist durch kaum was aufzuwiegen.

J
JMano Themenstarter:in
38 Beiträge seit 2013
vor 7 Jahren

I.d.R. erfolgt die Auflösung via Service Locator Pattern.
Je nachdem mit welcher Technologie Du arbeitest, gibt es da aber automatismen, zB in ASP.NET mit einem Dependency Resolver.

ASP.net hab ich noch nie was gemacht.
Service Locator Pattern - muss ich mir erst einlesen.
Aber so wie ich das sehe ist das dann eben eine statische singelton - wobei ich meinte, gelesen zu haben, das man sowas meist vermeiden soll.

aus wikipeda zum service locator:

The registry must be unique, which can make it a bottleneck for concurrent applications.  
The registry makes the code more difficult to maintain (opposed to using Dependency injection), because it becomes unclear when you would be introducing a breaking change.  
The registry makes code harder to test, since all tests need to interact with the same global service locator class to set the fake dependencies of a class under test.

😄

656 Beiträge seit 2008
vor 7 Jahren

Service Locator kann gefährlich sein, weil er recht einfach schlampigen Code erlaubt (du brauchst eine Instanz von IFoo? Frag den Service Locator, wurscht wo du grade bist) - drum ist er auch einer meiner Lieblings-Anti-Patterns, weil ich bisher nur schlechte Erfahrungen damit gemacht habe.
Solang du dir sowas aber im Hinterkopf behältst und nicht am Software Design vorbei arbeitest (nur weil dus damit kannst), sollte es auch kein Problem sein.

Der Service Locator Teil in dem Windsor Beispiel ist exakt an einer Stelle, weil zumindest irgendwo ein Service Locator Aufruf drin sein muss - üblicherweise ist das direkt in Program.Main für dein Haupt- oder Root-Objekt, von dem aus alles seinen Lauf nimmt (üblicherweise die Applikation, das Service oder wie auch immer es bei deiner Applikation auch grade aussieht).
Das kann auch eine Klasse sein, in der Praxis macht man aber halt üblicherweise Interfaces draus, wenn es sinnvoll und zweckmäßig ist (aus den von Abt genannten Gründen).

An anderen Stellen (zb. wenn deine Applikation einen IOutputWriter braucht) übernimmt der IoC Container die Arbeit für dich und versucht, die passende Komponente dafür zu finden (er macht den Service Locator indirekt) - ohne dass du irgendwo ein GottKlasse.GibMir<IOutputWriter>() oder ähnliches schreiben musst.
Alles was du selber dazu tun musst, ist ihm bekannt zu geben, welche Abhängigkeiten du haben willst - und das wurde schon zu Genüge über Konstruktoren usw. in früheren Posts hier drin angesprochen.

F
10.010 Beiträge seit 2004
vor 7 Jahren

Wenn man einfach wieder einen SL untestbar fest verdrahtet ist es sicherlich ein Antipattern.

Aber es gibt ja Microsoft.Practices.ServiceLocation, mit dem Interface IServiceLocator.
Das muss halt jeder injected bekommen der andere Klassen dynamisch erzeugen muss.

P
1.090 Beiträge seit 2011
vor 7 Jahren

Erstmal zu dem Gottobjekt.
Ein IoC-Container /Service Locator ist kein Gottobjekt, er hat genau eine Aufgabe. Nämlich die Abhängigkeiten aufzulösen. Er kennt jetzt auch nicht wirklich, die anderen Klasse und benutzt deren Methoden.

Auch sollte man die Abhängigkeiten von außen auflösen lassen, am besten Über Konstruktor Injektion.

Mal Vereinfacht dargestellt.

CustomerController(ICustomerService service)
{}

CustomerService : ICustomerService
{
    CustomerService(ICustomerRepository reposetory)
    {}
}

CustomerRepository : ICusomerRepository
....
u.s.w.

Ein IoC Container kann jetzt von außen alle Abhängigkeiten auflösen. Das ist grundlegend die beste Lösung.

Wenn du mit einen neuen Projekt (Green Field) anfängst, kannst du es direkt so anlegen, das von außen alle Abhängigkeiten aufgelöst werden können. Und du am ende nur genau eine Stelle hast wo dieses Geschieht.
Bei älteren Projekten ist dieses nicht unbedingt Möglich, so das man da Teilweise Tricksen muss.

**Vorteile **
Ich erhalte damit eine hoch flexible Software bei der ich ganz einfach Komponente austauschen kann. (Ist für mich der Zentrale Punkt)

(Bessere Testbarkeil (Unit Tests) sehe ich jetzt nicht direkt nach Kennt Beck (TTD), hab ich an dem Punkt wenn ich z.B. Mocks benutzen muss schon was Falsch gemacht. (Bei Lagacy Projekten kann es durchaus vorkommen).

**Nachteile **
Die Entwickler müssen, das Konzept verstanden haben, ist schon ein wenig Komplizierter als einfach ein var obj = new Object(). Es sollte aber Grundlegend im Interesse der Firma sein, das sie gut Ausgebildete Entwickler haben, die solche Konzepte beherrschen.

Bei sehr kleinen Projekten, kann es ein recht großer Overhead sein.

Und ich muss schauen, dass die Abhängigkeiten richtig aufgelöst werden. Je nach IoC Container muss ich das im Build bzw. Release Management mit berücksichtigen.

**Fazit **
Ja es gibt auch Nachteile, ich finde das sollte man auch zugeben.
Aber die Flexibilität, die ich dadurch gewinne ist einfach Enorm, so das es sich einfach Lohnt.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

656 Beiträge seit 2008
vor 7 Jahren

Aber es gibt ja Microsoft.Practices.ServiceLocation, mit dem Interface IServiceLocator.
Das muss halt jeder injected bekommen der andere Klassen dynamisch erzeugen muss.

Vielleicht der Vollständigkeit halber, Windsor bietet sowas auch an - dort läuft das Ganze aber bevorzugt über den Begriff Factory (bzw. über TypedFactory, wo Windsor automatisch eine Service Locator-ähnliche Implementierung per Proxy generiert) - ansonsten selbes Prinzip, irgendwas reingeben, was die Abhängigkeit auflösen kann.

F
10.010 Beiträge seit 2004
vor 7 Jahren

Schon richtig, aber das ist eben nicht testbar, da es nicht austauschbar ist.

Der IServiceLocator ist aber generisch und es gibt Implementierungen für fast jeden Container

W
955 Beiträge seit 2010
vor 7 Jahren

Hier brauche Ich ja überall wo ich ein Interface zu einer Implementierung auflösen will eine Instanz des Containers. Nein, musst du nicht. Lifestyle Singleton wird automatisch injiziert, Transiente Objekte werden über eine Facility reingereicht weil der Container ja nicht wissen kann wieviele Objekte du brauchst. In dem Beispiel wid nur der Service Locator verwendet weil du ja ein erstes Objekt bei einer DesktopApp brauchst, ApplicationController o.ä. Bei einem WebAPI-Projekt z.B. injiziert Castle.Windsor die Abhängigkeiten in die Controller automatisch.

656 Beiträge seit 2008
vor 7 Jahren

Schon richtig, aber das ist eben nicht testbar, da es nicht austauschbar ist.

Der IServiceLocator ist aber generisch und es gibt Implementierungen für fast jeden Container

So ganz versteh ich dich da jetzt aber leider nicht...der Unterschied bei Windsor ist es, dass ich statt einem hol-dir-was-du-willst IServiceLocator spezifisch eine IOutputWriterFactory als Ctor-Parameter möchte, die unmissverständlich aussagt "ich brauch einen Output Writer, von irgendwo, durch irgendjemanden bereit gestellt". Und ich persönlich finde das testbarer als ein zb. Interface mit einer Get<T> Methode, die potenziell erst recht wieder alles machen könnte, was man sich vorstellen und nicht vorstellen kann.

Das einzige wo ich dir da (mehr oder weniger) zustimme ist die Namensgebung, weil das Ding ja theoretisch auch Singletons zurückgeben kann und damit nicht notwendigerweise eine Factory darstellt (die traditionell immer neue Objekte liefert)...aber man muss das Interface ja nicht I...Factory nennen, nur weil die Facility TypedFactory heißt.

F
10.010 Beiträge seit 2004
vor 7 Jahren

Da habe ich mich wohl missverständlich ausgedrückt.
Auch mit dem IServiceLocator kannst Du, je nach bedarf auch Factories injecten.

Aber wenn jeder die IServiceLocator Facade benutzen würde, wären viele Bibliotheken einfacher zu konfigurieren, zu warten und auch zusammenzuführen.

SCSF und PRISM z.b. waren gute Beispiele im ersten step, wie man das ganze viel zu eng verzahnen kann.
SCSF kam mit ObjectBuilder als IOC und hatte den auch Fest in die UnitOfWorks integriert.
Wenn man dann z.b. Retina oder Activerecord benutzen wollte hatte man Castle mit dabei, andere Bibliotheken benutzten wieder andere Container.....

Wenn man schon gegen austauschbare Interfaces programmiert, warum das wichtigste Hilfsmittel davon ausschließen?

J
JMano Themenstarter:in
38 Beiträge seit 2013
vor 7 Jahren

Service Locator kann gefährlich sein, weil er recht einfach schlampigen Code erlaubt (du brauchst eine Instanz von IFoo? Frag den Service Locator, wurscht wo du grade bist) - drum ist er auch einer meiner Lieblings-Anti-Patterns, weil ich bisher nur schlechte Erfahrungen damit gemacht habe.
Solang du dir sowas aber im Hinterkopf behältst und nicht am Software Design vorbei arbeitest (nur weil dus damit kannst), sollte es auch kein Problem sein.

Wie sieht den die alternative aus?
Wie löse ich das den sonst auf?
IInterface auf Implementiuerung - da muss es doch zu einem Container.Resolve(IInterface) kommen (ist das noch nicht das Service Locator Pattern?)

Der Service Locator Teil in dem Windsor Beispiel ist exakt an einer Stelle, weil zumindest irgendwo ein Service Locator Aufruf drin sein muss - üblicherweise ist das direkt in Program.Main für dein Haupt- oder Root-Objekt, von dem aus alles seinen Lauf nimmt (üblicherweise die Applikation, das Service oder wie auch immer es bei deiner Applikation auch grade aussieht).
Das kann auch eine Klasse sein, in der Praxis macht man aber halt üblicherweise Interfaces draus, wenn es sinnvoll und zweckmäßig ist (aus den von Abt genannten Gründen).

mit fem Haupt/root Object meinst du, das ich nur davor die abhöngigkeiten auflöse und über dieses dann im Konstruktor alle Abhängigkeiten mit gebe? Wäre das nicht total überladen?
dann muss ich ja von dem Punkt aus alles durchschleifen ...

An anderen Stellen (zb. wenn deine Applikation einen IOutputWriter braucht) übernimmt der IoC Container die Arbeit für dich und versucht, die passende Komponente dafür zu finden (er macht den Service Locator indirekt) - ohne dass du irgendwo ein GottKlasse.GibMir<IOutputWriter>() oder ähnliches schreiben musst.
Alles was du selber dazu tun musst, ist ihm bekannt zu geben, welche Abhängigkeiten du haben willst - und das wurde schon zu Genüge über Konstruktoren usw. in früheren Posts hier drin angesprochen.

Aber genau das ist es scheinbar was ich nicht verstehe 😦
Wann wird das aufgelöst und injiziert?
bzw wie sieht die verwendung davon im konkreten code aus?
Definition (ich muss ja trotzdem vorher sagen für das Interface die implementierung) und Nutzung (wenn ich auf einem objekt was aufrufe, das dann intern was korrekt passiert)

Erstmal zu dem
>
.
Ein IoC-Container /Service Locator ist kein Gottobjekt, er hat genau eine Aufgabe. Nämlich die Abhängigkeiten aufzulösen. Er kennt jetzt auch nicht wirklich, die anderen Klasse und benutzt deren Methoden.

Auch sollte man die Abhängigkeiten von außen auflösen lassen, am besten Über Konstruktor Injektion.

Mal Vereinfacht dargestellt.

CustomerController(ICustomerService service)  
{}  
  
...  
  

Ein IoC Container kann jetzt von außen alle Abhängigkeiten auflösen. Das ist grundlegend die beste Lösung.

Das macht auch irgenwie Sinn ...
Aber was du mit von außen auflösen meinst ist mir nicht ganz klar. Von außen Injizieren? Aber gibt das nicht Probleme bei tieferen Hierarchien?

Nein, musst du nicht. Lifestyle Singleton wird automatisch injiziert

Von wem wird das injiziert? Ich meinte ja eigentlich konkret den Container, da man ihn ja überal zum Resolven braucht.
... und wenn der Container noch nicht konfiguriert ist.

... Transiente Objekte
werden über eine Facility reingereicht weil der Container ja nicht wissen kann wieviele Objekte du brauchst.

... was für Facility?
Und warum muss er wissen wieviele es sind. Er kann sie ja neu erstellen oder bauen.

In dem Beispiel wid nur der Service Locator verwendet weil du ja ein erstes Objekt bei einer DesktopApp brauchst, ApplicationController o.ä. Bei einem WebAPI-Projekt z.B. injiziert Castle.Windsor die Abhängigkeiten in die Controller automatisch.

In dem Beispiel sieht man doch gar nicht was es ist ... ?

P
1.090 Beiträge seit 2011
vor 7 Jahren

Das macht auch irgenwie Sinn ...
Aber was du mit von außen auflösen meinst ist mir nicht ganz klar. Von außen Injizieren? Aber gibt das nicht Probleme bei tieferen Hierarchien?

Grob hast du dann beim start sowas.

 public static void main(String[] args) {

 RegistContainer(); //Hier Registriert du die ganzen Abhänigkeiten[1]
  
IMyApp = container.Resolver<IMyApp>(); //Hier werden dann die ganzen Abhänigkeiten des Programmes aufgelöst[2]


}

[1] Wie du die Abhängigkeiten auflösen kannst, hängt stark vom IoC-Contaioner ab. Hier must du mal schauen. Grundlegend ist da für die meisten Anforderungen (Im Quellcode, als Konfigdatei (XML), DLL (Konvention 1. Gefundenes passendes Interface), über Attriebute u.s.w., ) alles zu finden. Der IoC muss nur Irgendwie Konfiguriere werden, damit er weiß welche Konkrete Instanz zu einem Interface er erzeugen muss.

[2]Grundlegend reicht da 1 einzelner Aufruf beim Start des Programms um alle Abhängigkeiten aufzulösen. Der IoC-Container muss dann aber auch Zugriff auf die Erzeugung aller Objekte haben. Irgendwo selbst ein Objekt zu Erzeugen und zu hoffen das der IoC-Container die Abhängigkeit auflöst funktioniert nicht.

Also wenn du durchgehend, alle Abhängigkeiten durch den Konstruktor mit gibst. Ist es die Beste Möglichkeit. (Mit Property Injection geht es auch. Ist nur nicht so schon, das nicht alle IoCs das Unterstützen und jemand, der deine Klasse benutzen möchte nicht Unbedingt weiß das da eine Abhängigkeit gebraucht wird).

Was leider nicht immer geht (Lagecycode). Hier das du dann vielleicht eine Klasse, die selbst eien andere Erzeugt usw. bis du an den Punkt kommst an dem du die eine Klasse durch deine neu Implementierung ersätze musst. Das bekommt der IoC dann nicht Aufgelöst. Hier kannst du dann selbst eine Konkrete Instanz deiner Klasse Erzeugen (nicht so schön) oder die Abhängigkeit z.B. mit dem ServiceLocator Patter auflösen (schon besser). Hier kennt die Klasse aber dann direkt oder Indirekt den IoC, was nicht so schön ist. Vor allem wenn es damit Endet, das zum Schluss jede Klasse den IoC kennt. Das sollte möglichst vermieden werden. Wenn du den IoC dann gegen einen anderen austauschen möchte, kann einiges an Arbeit auf dich zukommen.

Also für mich ist grundlegend die Konstruktor Injektion, die beste Lösung.

p.s. Meiner Meinung nach gibt es aber auch Klassen die sich nicht wirklich für IoC eignern.
Der Logger ist meines Erachtens, so eine Klasse. Da möchte ich einfach überall schnell und einfach drauf zugreifen können (auch in Lagecycode). Bei ihm ist für mich eine Factory, da die bessere Lösung. Das sehen aber manche hier anders. Die Diskussion, dazu kannst du hier finden.
Wie erstelle ich ein Objekt einmal und verwende es dort, wo ich es brauche in C#? --> DI/IoC

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern