Ich versuche, Unity dazu zu bringen, die Erstellung meiner Objekte zu verwalten, und ich möchte einige Initialisierungsparameter haben, die erst zur Laufzeit bekannt sind:
Im Moment kann ich mir nur vorstellen, wie das geht, wenn ich eine Init-Methode auf der Schnittstelle habe.
interface IMyIntf {
void Initialize(string runTimeParam);
string RunTimeParam { get; }
}
Um es dann (in Unity) zu verwenden, würde ich Folgendes tun:
var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");
In diesem Szenario wird der runTimeParam
Parameter zur Laufzeit basierend auf Benutzereingaben bestimmt. Der triviale Fall hier gibt einfach den Wert von zurück, runTimeParam
aber in Wirklichkeit ist der Parameter so etwas wie Dateiname und die Initialisierungsmethode macht etwas mit der Datei.
Dies führt zu einer Reihe von Problemen, nämlich dass die Initialize
Methode auf der Schnittstelle verfügbar ist und mehrfach aufgerufen werden kann. Das Setzen eines Flags in der Implementierung und das Auslösen einer Ausnahme bei wiederholtem Aufruf von Initialize
scheint sehr umständlich.
An dem Punkt, an dem ich meine Schnittstelle auflöse, möchte ich nichts über die Implementierung von wissen IMyIntf
. Was ich jedoch möchte, ist das Wissen, dass diese Schnittstelle bestimmte einmalige Initialisierungsparameter benötigt. Gibt es eine Möglichkeit, die Schnittstelle mit diesen Informationen irgendwie zu kommentieren (Attribute?) Und diese beim Erstellen des Objekts an das Framework zu übergeben?
Bearbeiten: Beschrieb die Oberfläche etwas mehr.
quelle
runTimeParam
ist eine Abhängigkeit, die zur Laufzeit anhand einer Benutzereingabe ermittelt wird. Sollte die Alternative dazu darin bestehen, es in zwei Schnittstellen aufzuteilen - eine zur Initialisierung und eine zum Speichern von Werten?Antworten:
An jedem Ort, an dem Sie einen Laufzeitwert benötigen, um eine bestimmte Abhängigkeit zu erstellen , ist Abstract Factory die Lösung.
Initialisierungsmethoden auf den Schnittstellen riechen nach einer undichten Abstraktion .
In Ihrem Fall würde ich sagen, dass Sie die
IMyIntf
Schnittstelle so modellieren sollten, wie Sie sie verwenden müssen - und nicht so, wie Sie beabsichtigen, Implementierungen davon zu erstellen. Das ist ein Implementierungsdetail.Daher sollte die Schnittstelle einfach sein:
Definieren Sie nun die Abstract Factory:
Sie können jetzt eine konkrete Implementierung erstellen
IMyIntfFactory
, die konkrete InstanzenIMyIntf
wie diese erstellt:Beachten Sie, wie wir so die Invarianten der Klasse mithilfe des
readonly
Schlüsselworts schützen können . Es sind keine stinkenden Initialisierungsmethoden erforderlich.Eine
IMyIntfFactory
Implementierung kann so einfach sein:In all Ihren Verbrauchern, in denen Sie eine
IMyIntf
Instanz benötigen , nehmen Sie einfach eine Abhängigkeit davon auf,IMyIntfFactory
indem Sie sie über Constructor Injection anfordern .Jeder DI-Container, der sein Geld wert ist, kann eine
IMyIntfFactory
Instanz automatisch für Sie verkabeln, wenn Sie sie korrekt registrieren.quelle
MyIntf
Implementierung durch die Factory mehr als erforderlich istrunTimeParam
(lesen Sie: andere Dienste, die von einem IoC aufgelöst werden sollen), müssen Sie diese Abhängigkeiten in Ihrer Factory weiterhin auflösen. Ich mag die Antwort von @PhilSandler, diese Abhängigkeiten an den Konstruktor der Fabrik zu übergeben , um dies zu lösen - ist das auch Ihre Meinung dazu?Wenn Sie auf diese Situation stoßen, müssen Sie normalerweise Ihr Design überdenken und feststellen, ob Sie Ihre Stateful- / Datenobjekte mit Ihren reinen Diensten mischen. In den meisten (nicht allen) Fällen sollten Sie diese beiden Objekttypen getrennt halten.
Wenn Sie einen kontextspezifischen Parameter benötigen, der im Konstruktor übergeben wird, können Sie eine Factory erstellen, die Ihre Dienstabhängigkeiten über den Konstruktor auflöst und Ihren Laufzeitparameter als Parameter der Create () -Methode (oder Generate () verwendet. ), Build () oder wie auch immer Sie Ihre Factory-Methoden nennen).
Setter oder eine Initialize () -Methode werden im Allgemeinen als schlechtes Design angesehen, da Sie sich "daran erinnern" müssen, sie aufzurufen und sicherzustellen, dass sie nicht zu viel vom Status Ihrer Implementierung preisgeben (dh was jemanden davon abhält, erneut zu arbeiten) -Calling Initialize oder der Setter?).
quelle
Ich bin auch einige Male auf diese Situation in Umgebungen gestoßen, in denen ich dynamisch ViewModel-Objekte basierend auf Modellobjekten erstelle (in diesem anderen Stackoverflow-Beitrag sehr gut beschrieben ).
Mir hat gefallen, wie die Ninject-Erweiterung es Ihnen ermöglicht, Fabriken basierend auf Schnittstellen dynamisch zu erstellen:
Bind<IMyFactory>().ToFactory();
Ich konnte keine ähnliche Funktionalität direkt in Unity finden . Deshalb habe ich meine eigene Erweiterung für den IUnityContainer geschrieben , mit der Sie Fabriken registrieren können, die neue Objekte basierend auf Daten von vorhandenen Objekten erstellen, die im Wesentlichen einer Typhierarchie einer anderen Typhierarchie zugeordnet sind : UnityMappingFactory @ GitHub
Mit dem Ziel der Einfachheit und Lesbarkeit habe ich eine Erweiterung erhalten, mit der Sie die Zuordnungen direkt angeben können, ohne einzelne Factory-Klassen oder Schnittstellen zu deklarieren (ein Echtzeit-Sparer). Sie fügen die Zuordnungen einfach genau dort hinzu, wo Sie die Klassen während des normalen Bootstrapping-Prozesses registrieren ...
Dann deklarieren Sie einfach die Mapping-Factory-Schnittstelle im Konstruktor für CI und verwenden die Create () -Methode ...
Als zusätzlichen Bonus werden alle zusätzlichen Abhängigkeiten im Konstruktor der zugeordneten Klassen auch während der Objekterstellung aufgelöst.
Natürlich wird dies nicht jedes Problem lösen, aber es hat mir bisher ziemlich gute Dienste geleistet, so dass ich dachte, ich sollte es teilen. Weitere Dokumentationen finden Sie auf der Projektseite auf GitHub.
quelle
Ich kann nicht mit einer bestimmten Unity-Terminologie antworten, aber es hört sich so an, als würden Sie gerade etwas über Abhängigkeitsinjektion lernen. In diesem Fall fordere ich Sie dringend auf, das kurze, übersichtliche und informationsreiche Benutzerhandbuch für Ninject zu lesen .
Dies führt Sie durch die verschiedenen Optionen, die Sie bei der Verwendung von DI haben, und wie Sie die spezifischen Probleme berücksichtigen, mit denen Sie unterwegs konfrontiert werden. In Ihrem Fall möchten Sie höchstwahrscheinlich den DI-Container verwenden, um Ihre Objekte zu instanziieren, und dieses Objekt über den Konstruktor einen Verweis auf jede seiner Abhängigkeiten erhalten lassen.
In der exemplarischen Vorgehensweise wird auch erläutert, wie Methoden, Eigenschaften und sogar Parameter mithilfe von Attributen mit Anmerkungen versehen werden, um sie zur Laufzeit zu unterscheiden.
Selbst wenn Sie Ninject nicht verwenden, erhalten Sie in der exemplarischen Vorgehensweise die Konzepte und die Terminologie der Funktionen, die Ihrem Zweck entsprechen, und Sie sollten in der Lage sein, dieses Wissen Unity oder anderen DI-Frameworks zuzuordnen (oder Sie davon zu überzeugen, Ninject auszuprobieren). .
quelle
Ich denke, ich habe es gelöst und es fühlt sich ziemlich gesund an, also muss es halb richtig sein :))
Ich habe mich
IMyIntf
in eine "Getter" - und eine "Setter" -Schnittstelle aufgeteilt. So:Dann die Implementierung:
IMyIntfSetter.Initialize()
kann immer noch mehrmals aufgerufen werden, aber mit Teilen des Service Locator-Paradigmas können wir es ganz gut zusammenfassen, so dassIMyIntfSetter
es sich fast um eine interne Schnittstelle handelt, die sich von dieser unterscheidetIMyIntf
.quelle