wie Sie Bibliotheken schreiben, ohne Benutzer zur Verwendung des IOC-Containers der Bibliothek zu zwingen

8

Die kurze Frage ist: Gegeben eine Bibliothek garantiert die Verwendung eines bestimmten IOC - Containers für seine Interna, wenn eine Anwendung diese Bibliothek nutzt, vorausgesetzt, die App garantiert einen IOC - Container für die Verdrahtung ihrer Abhängigkeiten zu verwenden die beiden Behälter sind anders, wie können sie gut zusammenspielen?

Das Szenario besteht darin, dass in der Anwendung Klassen definiert sind, die von Typen aus der Bibliothek abhängen. Wenn der Anwendungscontainer versucht, eine solche Klasse zu erstellen, muss er wissen, wie der in der Bibliothek vorhandene Typ aufgelöst werden kann.

Hier ist die langatmige Frage:

Diese Frage scheint in verschiedenen Formen und Formen zuvor schon auf SO gestellt worden zu sein, aber ich kann nicht die Antwort finden, die ich brauche, also werde ich es mit einem hypothetischen _over_simplified_ konkreten Beispiel versuchen. Wir möchten eine Bibliothek für die Protokollierung schreiben, die Benutzer als Paket in ihre Lösung aufnehmen können, um die Protokollfunktionalität sofort zu nutzen. Die öffentlichen Schnittstellen, über die die Bibliothek verfügt, sind ..

public interface ILogger {}

public interface ITarget {}

Konkrete Implementierungen sind

internal class Logger: ILogger { public Logger(ITarget target) {}}

internal class FileTarget : ITarget {}

Wenn der Benutzer unser Paket enthält und eine Klasse mit einer Eigenschaft vom Typ ILogger definiert oder ein ctor-Argument vom Typ ILogger hat, dann ist unsere Bibliothek dafür verantwortlich, eine konkrete Implementierung für diese Schnittstelle in den Benutzer einzufügen Klasse. Standardmäßig wird der injizierte Logger zum Logging an das Dateisystem weitergeleitet, da die Standardimplementierung von ITarget , die in die ILogger -Implementierung eingefügt wurde, ein FileTarget von unserer Bibliothek ist.

Wenn der Benutzer beschließt, eine Klasse zu schreiben, die die ITarget -Schnittstelle implementiert, wird unsere Bibliothek diese verwenden, um in die Logger -Klasse zu injizieren und nicht die standardmäßige FileTarget -Implementierung.

SO, was ich zeigen möchte, ist ihre bidirektionale Abhängigkeit hier.

  1. Unsere Bibliothek hängt von den Assemblys des Benutzers ab, da sie die Assemblys des Benutzers scannen muss, um beliebige Erweiterungspunkte (dh eine ITarget -Implementierung) zu laden und diese vor einer Standardimplementierung in ihre eigenen Objekte zu injizieren. p>

  2. Die Assemblys des Benutzers hängen von der Bibliothek ab. Wenn der Benutzer eine Klasse mit einer ILogger -Schnittstelle als Abhängigkeit definiert, sollte dieses Benutzerobjekt einen konkreten Verweis auf diese Schnittstelle erhalten, die zur Laufzeit von unserem bereitgestellt wird Bibliothek.

Die einfache Lösung ist, wenn der Benutzer und unsere Bibliothek beide denselben IOC-Container verwenden, dann ist das Problem gelöst. Aber das ist eine starke Annahme. Was ich tun möchte, ist

  1. Verwenden Sie einen IOC-Container mit der Bibliothek, die der Anforderung der Bibliothek am besten entspricht, in meinem Fall dem Ninject.
  2. Zur Laufzeit stelle ich irgendwie einen Mechanismus für den Benutzer bereit, um über eine API in meine Bibliothek zu rufen, die sicherstellen wird, dass Ninject hochfährt und die Assemblys des Benutzers scannt und alles unter Berücksichtigung aller Erweiterungspunkte verdrahtet.

So weit so gut, es ist perfekt erreichbar, aber hier kommt der knifflige Teil.

  • wenn der Benutzer auch Ninject benutzt, dann wird das Problem automatisch gelöst, da Ninject bereits weiß, wie man Interfaces in unserer Bibliothek auflösen kann. Aber was ist, wenn der Benutzer entscheidet, seinen / ihren IOC-Behälter zu verwenden?

Ich möchte fast eine Art von untergeordneten Container-Funktionalität in der Bibliothek mit einer Schnittstelle wie folgt

definieren

public interface IDependencyResolvingModule { T Get<T>(); []T GetAll<T>(); }

und stellen eine Implementierung bereit, die den Container unserer Bibliothek (d. h. Ninect) verwendet, um den in den beiden oben definierten Methoden angeforderten Typ aufzulösen.

Ich möchte, dass der IOC-Container des Benutzers einige Funktionen hat. Wenn er eine Abhängigkeit nicht auflösen kann (d. h. ILogger ), sollte er sich in die IDependencyResolvingModule -Implementierung einklinken und nach der Abhängigkeit fragen.

Auf diese Weise kann unsere Bibliothek den IOC-Container auswählen, und der Code des Benutzers kann Abhängigkeiten auflösen, von denen sein IOC-Container keine Ahnung hat. Funktioniert diese Lösung nicht, wenn IOC Container einige der bereitgestellten Funktionen zur Registrierung von Singleton-Instanzen von IDependencyResolverModules , die in Assemblys im Verzeichnis der ausführenden Assembly gefunden wurden, und wenn sie einen Typ nicht auflösen können, eines der Singleton-Module fragt? / p>

Aber abgesehen von einer Lösung, die jeden anderen IOC-Container unterbringen muss, wie sonst kann das gelöst werden? Das Problem in ein paar Zeilen ist, wenn eine Third-Party-Assembly einen IOC-Container für seine Interna verwendet, was eine einfache Lösung ist, so dass diese Bibliothek einfach einen Mechanismus für einen IOC-Container bereitstellen kann, der sich in Abhängigkeiten einklinken und auflösen kann die in der Bibliothek leben.

    
afif 24.03.2012, 14:11
quelle

1 Antwort

3

Ich sehe hier einige mögliche Ansätze:

  1. Schreiben Sie den Standard-Registrator für alle gängigen IoC-Container. Jeder von ihnen sollte in der separaten Baugruppe platziert werden. Dann kann der Entwickler den gewünschten auswählen und seinen Container damit konfigurieren.

  2. Definieren Sie Ihre eigene Factory-Abstraktion und schreiben Sie eine Standardimplementierung, die den Standardlogger zurückgibt. Lassen Sie den Entwickler die Implementierung dieser Fabrik ersetzen. Zum Beispiel mit Adapter für seinen Lieblingscontainer. Dieser Ansatz ist am Container-Agnostisch, da der Entwickler einfach die Standard-Factory-Implementierung verwenden kann. Aber dieser Weg hat nichts mit Autoverdrahtung zu tun.

  3. Die faule Variante des ersten Ansatzes. Schreiben Sie ein kleines Handbuch zum Konfigurieren eines Containers, um mit Standardimplementierungen zu arbeiten. Dann könnte der Entwickler den Container selbst konfigurieren.

  4. Kombinieren Sie alle vorherigen Lösungen, um jeden Entwickler zufrieden zu stellen. :)

EDIT: hinzugefügtes Beispiel der Integration von zwei Containern

%Vor%     
Dmitriy Startsev 26.03.2012 22:42
quelle