| 

.NET C# Java Javascript Exception

5
Hallo zusammen.

Ich habe folgende Klassendefinition:

public class CommNodeHandler : ICommNodeHandler
{
public CommNodeHandler(IDatabase db)
{
...
}

public CommNodeHandler(IDatabase db, INetCommNode netCommNode)
{
...
}
}


Die Klasse wird im IOC Container wie folgend registriert.

builder.Register<ICommNodeHandler, CommNodeHandler>();


Zum Erstellen des Objekts rufe ich folgenden Code auf.

var commNodeHandler = 
Ioc.Container.Resolve<ICommNodeHandler>(
new AnonymousArgument(new { db = dal, netCommNode = (INetCommNode)node }));


Das Problem ist jetzt das beim Erstellen des Objekts immer der Konstruktor mit einem Parameter aufgerufen wird und nicht der Konstruktor mit 2 Parametern, obwohl ich doch beide Parameter übergebe.

Wenn ich den Konstruktor mit dem einen Parameter lösche, dann wird der Konstruktor mit den 2 Parametern aufgerufen und beide Parameter werden richtig übergeben.

Wie kann ich LightCore dazu bringen den richtigen Konstruktor aufzurufen ?

Verwendete LightCore Version: 1.4.1.0

Viele Grüße
Jörg
16.10.2012
multi1209 848 1 8
Mit builder.Register<ICommNodeHandler, CommNodeHandler>(); registrierst du die Klasse aber OHNE Parameter.
Mario Priebe 17.10.2012
3 Antworten
3
Ich handhabe es grundsätzlich so, dass von Containern aufgelöste Objekte nur einen Konstruktor haben. Der Bedarf mehrere Konstruktoren zu unterstützen ist der Tatsache geschuldet, dass das Objekt nicht eindeutig konstruierbar ist.
Aus diesem Grund implementiere ich für derartige Objekte eine Abstract Factory und injiziere diese Factory statt der Abhängigkeit. Die erst zur Laufzeit bekannten Parameter werden dann der Factory übergeben, die wiederum die Konstruktion der Abhängigkeit übernimmt. Die Factory selbst kannst Du in den Container packen.

Noch eine (Rück)Frage:
Rufst Du den Container nur während dem Bootstrapping direkt auf oder reichst Du den Container rum, so dass der Container als allwissende Factory (=Service Locator Pattern) benutzt werden kann?

Update in Bezug auf Deinen Kommentar:
Ein Container sollte nach dem Start nicht mehr zugänglich sein (siehe auch Service Locator is an anti pattern). Das Problem an diesem Ansatz ist, dass Du Deinen Klassen Ihre Abhängigkeiten nicht mehr ansiehst. Die Möglichkeit, mit Hilfe eines allgemein zugänglichen Containers (ServiceLocators) "prinzipiell alles" erzeugen zu können, hat mehrere Nachteile. Der offensichtlichste ist, dass die Testbarkeit von Klassen, die den ServiceLocator verwenden, stark beeinträchtigt wird. Ich kann solche Klassen nicht mehr vollständig ohne die Abhängigkeit ServiceLocator testen.
Ich sehe auch an der Schnittstelle der Klasse nicht, welche Abhängigkeiten sie über den ServiceLocator auflöst, Abhängigkeiten sind also opak. Abhängigkeiten, die in den Konstruktor injiziert werden, sind hingegen transapranet und an der Schnittstelle (Konstruktor) der Klasse sofort erkennbar.
Konsequente ConstructorInjection machen den ServiceLocator obsolet. Tests gehen leichter von der Hand...usw.
Für tiefergehende Ausführungen zu dem Thema empfehle ich Dir den Blog von Mark Seeman und sein ausgezeichnetes Buch Dependency Injection in .NET.

Schnelles Beispiel für die Abstract Factory:

public interface ICommNodeHandlerFactory
{
ICommNodeHandler Create(IDatabase db);
ICommNodeHandler Create(IDatabase db, INetCommNode netCommNode);
}

public class CommNodeHandlerFactory : ICommNodeHandlerFactory
{
public CommNodeHandlerFactory(object dep1, object dep2) // dep1, dep2 Platzhalter für Abhängigkeiten der Factory
{}
// ... Create() Methoden implementieren
}

// Registrieren
builder.Register<ICommNodeHandlerFactory, CommNodeHandlerFactory>();

// Erzeugung
var factory = container.Resolve<ICommNodeHandlerFactory>();
var commNodeHandler = factory.Create(dal, (INetCommNode)node);


Bis auf die Erzeugung mache ich das nicht anders. Ich löse die Factory nicht über den Container auf, sondern lasse sie in die Konstruktoren der Verwender injizieren.


Florian
18.10.2012
ffordermaier 8,4k 3 9
Dein Ansatz hört sich interessant an, werde es mal ausprobieren. Zu deiner Frage: Ich erstelle den Container einmal beim Start und stelle den Container dann in einer static class allen anderen Klassen zur Verfügung. Hatte auch mal über einen ServiceLocator nachgedacht, aber dort war es mit garnicht möglich gewesen Parameter an den Konstruktor zu übergeben. Vielleicht habe ich auch einfach nur was falsch gemacht.
multi1209 18.10.2012
Siehe mein Update.
ffordermaier 18.10.2012
Wäre es möglich, dass du ein kurzes Beispiel postest, wie du die Factory registrierst und verwendest, ich stehe nämlich gerade auf dem Schlauch und verstehe nicht wie du das machst.
multi1209 18.10.2012
Hope this helps.
ffordermaier 18.10.2012
Danke für das Beispiel, jetzt ist es klar. Habe nur noch eine weitere Frage. Im Konstruktor der Factory gibst du 2 Platzhalter an. Wenn ich das genauso umsetze wie in deinem Beispiel, dann wirft das Resolve eine Exception, dass kein geeigneter Konstruktor gefunden wurde, wenn ich die Platzhalter lösche und einen parameterlosen Konstruktor habe, dann funktioniert es. Aus welchem Grund hast du die Platzhalter angegeben ?
multi1209 18.10.2012
1
Das waren nur "Platzhalter" für Abhängigkeiten, die die Factory unter Umständen haben könnte. Wenn Du für die Konstruktion keine weiteren Abh. in die Factory injizieren musst, reicht natürlich der parameterlose Konstruktor.
Hätte die Factory weitere Abh., würdest Du diese im Konstruktor übergeben. Wenn der Container dann die Factory auflöst, kann er die entsprechenden Konstruktorparameter gleich mit auflösen und Deine Factory ist ready-to-use.
ffordermaier 18.10.2012
Danke, jetzt ist alles klar :-)
multi1209 18.10.2012
1
+1 für die Idee mit der abstrakten Fabrik. Service Locator als Anti-Pattern finde ich aber zu krass. Vielleicht dazu mal mehr in der Lounge - grad fehlt mir aber die Zeit.
Matthias Hlawatsch 31.10.2012
Diskussion in der Lounge würde ich auch gerne führen. An Zeit mangelts mir im Momemt aber leider auch. Ich behalts im Hinterkopf, es findet sich sicher mal wieder Zeit...
ffordermaier 01.11.2012
2
Hallo multi

Das ist ein Fehler mit der dynamischen Parameterübergabe. Habe vor bald die nächste Version zu releasen, da ist der Fehler behoben.

Hier ist der Quellcode der kommenden Version. Damit funktioniert es.
- https://peterbucher.kilnhg.com/Code/LightCore/Group/Default

Auch in den Varianten:

container.Resolve<IFoo>(parameter1);
container.Resolve<IFoo>(parameter1, parameter2);


Anstelle über das AnonymousArgument zu gehen.

Die Möglichkeit mit der Factory kann man auch in Betracht ziehen.


Gruss Peter
30.10.2012
Peter Bucher 178 5
+1 Danke für die Aufklärung aus erster Hand.
Bei der Gelegenheit: Du hast einst benannte Registrierungen aus LightCore entfernt - soweit ich mich erinnere (Golo Rodens Blog-Post dazu ist leider nicht mehr online) mit der Begründung, dass damit durch die Hintertür doch wieder Wissen über die konkret genutzte Implementierung in den Code gelangt. Gilt das aber nicht noch viel mehr für Resolve mit Parametern? Schließlich garantiert mir ja kein Interface das Vorhandensein eines bestimmten Konstruktors.
Matthias Hlawatsch 30.10.2012
Hallo Matthias

Ja gute Frage ob die Entscheidung richtig war oder es dann doch egal wäre.

Meinst du damit, man könne die benannten Registrierungen gleih wieder einfügen?
Peter Bucher 30.10.2012
1
Hallo Peter!
Du könntest natürlich den puristischen und belehrenden Weg auch noch weitergehen und auch noch die dynamische Parameterübergabe von der Feature-Liste streichen. Immerhin gibt es mit dem Abstrakte-Fabrik-Muster eine (obendrein sehr sinnvolle) Alternative (siehe Florians Antwort) - ebenso, wie man die benannten Registrierungen durch "Tagging Interfaces" (leere Ableitungen vom eigentlich Nutz-Interface) simulieren kann.
Aber ich fände es tatsächlich besser, wenn LightCore beides könnte und es der Verantwortung der Nutzer überlassen bleibt, ob und wie sie welche Features einsetzen.
Matthias Hlawatsch 31.10.2012
Übrigens gibt es meiner Meinung nach sogar ein Einsatzszenario für benannte Registrierungen, das nicht der "reinen Lehre" widerspricht: sie können nämlich "Rollen" abbilden, also die Funktion, die eine bestimmte Objekt-Instanz innerhalb der Anwendung übernehmen soll. Denk an ein Schachspiel - da könnte es zwei Spieler geben, beide implementieren ISpieler, trotzdem will ich sie auseinanderhalten können (und z.B. je nach Konfiguration den einen oder anderen durch einen Computerspieler ersetzen). Mit benannten Regístrierungen "spielerWeiß" und "spielerSchwarz" kein Problem.
Matthias Hlawatsch 31.10.2012
Hallo Matthias

Du kannst sowieso nie feststellen ob eine Klasse hinter einem Interface irgendwelche Konstruktoren anbietet. Das zerstört aber nicht denn Sinn des Interfaces.

Es gibt eine Alternativ zu Named Registrations.

Beispiel:

IPlugin
BlurPlugin : IPlugin

container.Resolve<IPlugin>("blur");

kann ersetzt werden durch

IBlurBlugin : IPlugin

container.Resolve<IBlurPlugin>();

Dasselbe beim Spieler Beispiel:

IPlayer
IWhitePlayer : IPlayer
IBlackPlayer : IPlayer

container.Resolve<IBlackPlayer>();

Denn sonst hast du zwar mehrere Implementierungen aber sie verhalten sich nicht gleich.
Peter Bucher 04.11.2012
1
Hi Peter,
ich denke, genau das meint Matthias mit den "Tagging Interfaces". Ich gebe diesem Ansatz auch den Vorzug gg. Named Registrations.

Mein Fazit: Einen Container zu verwenden, um mit dessen Hilfe explizit einen speziellen Konstruktor aufzurufen, fühlt sich für mich falsch an. Es verschleiert ein einfaches "new" und ist ServiceLocation und keine DI. Und ich bin Mark Seeman's Meinung, dass ServiceLocator ein AntiPattern ist (siehe verlinkter Post in meiner Antwort).
ffordermaier 04.11.2012
1
Genau, Peters Skizze beschreibt das, was ich mit "Tagging Interfaces" meinte. Ist für mich aber nur ein Notbehelf, ich ziehe benannte Registrierungen vor. Stellt Euch mal vor, es geht um Bridge statt Schach. Dann sind es schon vier TIs (INordPlayer, IEastPlayer...) - das bläht den Code unnötig auf. Ganz abgesehen von praktischen Schwierigkeiten in bestimmten Fällen (z.B. Player ist sealed und liegt nur in einer fremden Assembly ohne Sourcecode vor).
Matthias Hlawatsch 04.11.2012
1
Code, der ein Resolve<ISomething>(...) aufruft, sollte mit *jeder* Implementierung von ISomething funktionieren. Ein Resolve mit (Konstruktor-)Argumenten läuft mMn viel eher Gefahr, dagegen zu verstoßen, als ein Resolve auf eine benannte Registrierung. Ersteres setzt auf jeden Fall die Existenz eines passenden Konstruktors voraus, ohne dass das Interface das garantieren kann. Letzteres beinhaltet nur möglicherweise die Gefahr impliziter Annahmen über die verwendete Implementierung. Deshalb fände ich es konsequent, beide Features zu unterstützen oder keines.
Matthias Hlawatsch 04.11.2012
Wenn die TIs tatsächlich zuviele werden sollten, bleibt immer noch der Weg über die Factory, die auch das sealed und Fremdassembly Problem löst - und zwar IMHO sauberer und verständlicher als ein Magic-String.
ffordermaier 05.11.2012
1
Versuchs mal so:

.WithArguments("Argument1").WithName("foo");
.WithArguments("Argument1", "Argument2").WithName("bar");


Ansonsten mal beo Roberto schauen, der hat dort ein ausführliches HowTo zu Lightcore geschrieben: http://www.aspnetzone.de/blogs/robertobez/archive/2010/01/16/inversion-of-control-di-ioc-container-lightcore.aspx
17.10.2012
Mario Priebe 6,0k 3 9
Hallo. Danke erstmal für deine Antwort. Den Link kannte ich schon und das Problem ist, dass das Tutorial nicht mehr ganz aktuell ist. In der Version 1.4 ist z.B. die Methode WithName nicht mehr vorhanden. Da zweite Problem ist, das ich nur Registrierungszeit die Argumente noch nicht kenne, da diese erst zur Laufzeit ermittelt werden, daher komme ich mit der Methode WithArguments auch nicht so recht weiter.
multi1209 18.10.2012

Stelle deine .net-Frage jetzt!