2009-02-17 11 views
7

Ich habe eine Frage, und ich werde diese subjektive tag da ist das, was ich denke, es entwickelt sich, mehr eine Diskussion. Ich hoffe auf gute Ideen oder Denker. Ich entschuldige mich für die langatmige Frage, aber Sie müssen den Kontext kennen. im GrundeServiceContainer, IoC, und Einweg-Objekte

Die Frage ist:

  • Wie geht man mit konkreten Typen in Bezug auf IoC Container? Insbesondere, wer ist für die Entsorgung verantwortlich, wenn sie entsorgt werden müssen, und wie wird dieses Wissen an den aufrufenden Code weitergegeben?

Müssen Sie IDisposable? Wenn nicht, ist dieser Code zukunftssicher, oder ist die Regel, dass Sie keine Einwegobjekte verwenden können? Wenn Sie IDisposable-Anforderungen für Schnittstellen und konkrete Typen erzwingen, um zukunftssicher zu sein, wessen Verantwortung sind Objekte, die im Rahmen von Konstruktoraufrufen eingespeist werden?


bearbeiten: Ich nahm die Antwort von @Chris Ballard, da es die nächsten zu dem Ansatz ist, die wir am Ende mit.

Grundsätzlich haben wir immer eine Art zurück, die wie folgt aussieht:

public interface IService<T> : IDisposable 
    where T: class 
{ 
    T Instance { get; } 
    Boolean Success { get; } 
    String FailureMessage { get; } // in case Success=false 
} 

Wir haben dann von einem Objekt der Umsetzung dieser Schnittstelle zurückzukehren beide .Resolve und .TryResolve, so dass das, was wir in der anrufenden Code zu erhalten ist immer der gleiche Typ.

Nun ist das Objekt, das diese Schnittstelle implementiert, IService<T> IDisposable, und sollte immer entsorgt werden. Es ist nicht Sache des Programmierers, der einen Dienst auflöst, um zu entscheiden, ob das Objekt IService<T> entsorgt werden soll oder nicht.

Jedoch, und dies ist der entscheidende Teil, ob die Dienstinstanz entsorgt werden soll oder nicht, wird dieses Wissen in das Objekt implementiert IService<T>, also wenn es sich um einen werksseitigen Dienst handelt (dh jeder Aufruf von Resolve endet mit einer neuen Dienstinstanz), dann wird die Dienstinstanz entsorgt, wenn das Objekt IService<T> entsorgt wird.

Dies hat es auch möglich gemacht, andere spezielle Bereiche, wie Pooling, zu unterstützen. Wir können jetzt sagen, dass wir mindestens 2 Dienstinstanzen benötigen, maximal 15 und typischerweise 5, was bedeutet, dass jeder Aufruf von .Resolve entweder eine Dienstinstanz aus einem Pool verfügbarer Objekte abruft oder eine neue erstellt. Wenn das Objekt IService<T>, in dem der Pool-Dienst enthalten ist, entsorgt wird, wird die Dienstinstanz wieder in ihren Pool freigegeben.

Sicher, machte dies den gesamten Code sieht wie folgt aus:

using (var service = ServiceContainer.Global.Resolve<ISomeService>()) 
{ 
    service.Instance.DoSomething(); 
} 

aber es ist ein sauberer Ansatz, und es hat die gleiche Syntax unabhängig von der Art des Dienstes oder der konkreten Gegenstand in Gebrauch, so wählten wir, dass als eine akzeptable Lösung.


Original Frage folgt, für die Nachwelt


Langwierige Frage kommt hier:

Wir haben einen IoC-Container, die wir verwenden, und vor kurzem entdeckten wir, welche Beträge zu einem Problem.

In nicht-IoC-Code, wenn wir, sagen wir, eine Datei verwenden, wollte, haben wir eine Klasse wie folgt:

using (Stream stream = new FileStream(...)) 
{ 
    ... 
} 

Es war keine Frage, ob diese Klasse war etwas, das eine begrenzte gehalten Ressource oder nicht, da wir wussten, dass Dateien geschlossen werden mussten, und die Klasse selbst IDisposable implementiert. Die Regel ist einfach, dass jede Klasse, von der wir ein Objekt konstruieren, das IDisposable implementiert, entsorgt werden muss. Keine Fragen gefragt. Es ist nicht Sache des Benutzers dieser Klasse, zu entscheiden, ob der Aufruf von Dispose optional ist oder nicht.

Ok, also auf den ersten Schritt in Richtung der IoC-Container. Nehmen wir an, wir möchten nicht, dass der Code direkt mit der Datei kommuniziert, sondern stattdessen eine Ebene der Indirektion durchläuft. Lassen Sie uns diese Klasse einen BinaryDataProvider für dieses Beispiel nennen. Intern wird die Klasse mit einem Strom, der noch ein Einweg-Objekt ist, so dass der obige Code geändert werden würde:

using (BinaryDataProvider provider = new BinaryDataProvider(...)) 
{ 
    ... 
} 

Diese ändert sich nicht viel. Das Wissen, dass die Klasse IDisposable implementiert, ist immer noch da, keine Fragen, wir müssen Dispose aufrufen.

Aber nehmen wir an, dass wir Klassen haben, die Daten bereitstellen, die im Moment keine so begrenzten Ressourcen verwenden.

BinaryDataProvider provider = new BinaryDataProvider(); 
... 

OK, so weit so gut, aber hier kommt das Fleisch der Frage:

Der obige Code könnte dann wie folgt geschrieben werden. Nehmen wir an, wir möchten einen IoC-Container verwenden, um diesen Anbieter zu injizieren, anstatt von einem bestimmten konkreten Typ abhängig zu sein.

würde der Code dann:

IBinaryDataProvider provider = 
    ServiceContainer.Global.Resolve<IBinaryDataProvider>(); 
... 

Bitte beachte, dass ich annehmen, dass es eine unabhängige Schnittstelle zur Verfügung steht, dass wir das Objekt durch zugreifen können.

Mit der obigen Änderung, was ist, wenn wir später ein Objekt verwenden möchten, das wirklich entsorgt werden sollte? Keiner der vorhandenen Codes, die diese Schnittstelle auflösen, wird geschrieben, um das Objekt zu beseitigen. Was nun?

Die Art, wie wir es sehen, haben wir eine Lösung holen:

  • Laufzeitkontrolle implementieren, die überprüft, wenn ein konkreter, die IDisposable implementiert registriert wird, erfordern, dass die Schnittstelle es durch auch Geräte ausgesetzt ist IDisposable.Dies ist keine gute Lösung
  • Enfore eine Einschränkung für die Schnittstellen verwendet werden, müssen sie immer von IDisposable erben, um zukunftssichere
  • Erzwingen Laufzeit zu sein, dass keine konkreten Typen IDisposable sein kann, da dies insbesondere nicht Wird der Code mit dem IoC-Container behandelt
  • Lassen Sie es einfach dem Programmierer überlassen, ob das Objekt IDisposable implementiert und "das Richtige tut"?
  • Gibt es noch andere?

Und was ist mit dem Injizieren von Objekten in Konstruktoren? Unser Container und einige der anderen Container, die wir untersucht haben, können einem Konstrukteur eines Betontyps ein neues Objekt in einen Parameter einfügen. Zum Beispiel, wenn unsere BinaryDataProvider ein Objekt benötigen, das die ILogging Schnittstelle implementiert, wenn wir IDispose- "Fähigkeit" auf diesen Objekten erzwingen, deren Verantwortung es ist, das Logging-Objekt zu entsorgen?

Was denkst du? Ich will Meinungen, gute und schlechte.

Antwort

2

(Disclaimer: Ich bin dieses Java-Sachen basiert Beantwortung auf Obwohl ich C# programmieren habe ich nichts in C# proxied aber ich weiß, dass es möglich ist, Sorry über die Java-Terminologie..)

Sie könnten die IoC lassen Framework inspizieren das Objekt konstruiert, um zu sehen, ob es IDisposable unterstützt. Wenn nicht, könnten Sie einen dynamischen Proxy verwenden, um das tatsächliche Objekt, das das IoC-Framework bereitstellt, in den Clientcode zu integrieren. Dieser dynamische Proxy könnte IDisposable implementieren, sodass Sie immer ein IDisposable an den Client senden. Solange Sie mit Schnittstellen arbeiten, die ziemlich einfach sein sollten?

Dann haben Sie nur das Problem der Kommunikation mit dem Entwickler , wenn das Objekt ein IDisposable ist. Ich bin mir nicht sicher, wie das auf eine nette Art und Weise gemacht wird.

+0

Nun, das klingt wie "immer ein IDisposable Objekt aus dem IoC-Container zurückgeben" und dann wäre die einfachste Lösung, um ein Kriterium für die konkreten Typen zu machen. Die Implementierung dieser Schnittstelle könnte natürlich leer bleiben, so dass es nicht viel Code-übergreifend gibt, nur eine Methode. –

+0

Ja, ich nehme an, es gibt wenig Unterschied. Ich nehme an, das grundlegende Problem ist, dass Sie die Verfügbarkeit des Objekts mit dem Client-Code kommunizieren müssen, also denke ich, es gibt wenig Hoffnung;) – krosenvold

+0

Ich würde gehen, keine Einweg-Objekte zu unterstützen. – krosenvold

3

Eine Option könnte mit einer Fabrik Mustern zu gehen, so dass die Objekte direkt von den IoC-Containern nie selbst entsorgt werden müssen erstellt, zB

IBinaryDataProviderFactory factory = 
    ServiceContainer.Global.Resolve<IBinaryDataProviderFactory>(); 
using(IBinaryDataProvider provider = factory.CreateProvider()) 
{ 
    ... 
} 

Nachteil Komplexität hinzugefügt wird, aber es bedeutet, dass Der Container erzeugt niemals etwas, über das der Entwickler verfügen soll - es ist immer ein expliziter Code, der das tut.

Wenn Sie es wirklich offensichtlich machen möchten, könnte die Factory-Methode so etwas wie CreateDisposableProvider() heißen.

+0

Der ganze Punkt des IoC-Containers ist, dass ich später, durch Konfiguration, ein anderes Objekt zurückgeben kann. In Ihrem Beispiel muss jedes dieser Objekte IDisposable implementieren. Meine Frage ist: Ist das ein Kriterium für diese Objekte? Kann ich dieses Kriterium etwas spezifizieren, oder ist es pro Schnittstelle? Zukunftssicher? –

+0

In diesem Beispiel kann ich verschiedene Implementierungen der Factory zurückgeben, aber jeder muss einen Provider erstellen, der IDisposable implementiert. Dies ist nicht ideal, aber gibt Ihnen zumindest einen Hinweis auf die Semantik. Vielleicht ist das nicht die Frage, die du gestellt hast? –

+0

Nein, meine Frage ist: Sollte ich durch den IoC-Container erzwingen, dass alle diese Schnittstellen und konkreten Typen IDisposable implementieren, oder sollte ich das als Übung für den Benutzer belassen? –

1

Sie kamen tatsächlich mit einer sehr schmutzigen Lösung: Ihr IService Vertrag verletzt die SRP, die ein großes No-No ist.

Ich empfehle, so genannte "Singleton" -Dienste von sogenannten "Prototyp" -Dienste zu unterscheiden. Die Lebensdauer von "Singleton" -Einträgen wird vom Container verwaltet, der zur Laufzeit abfragen kann, ob eine bestimmte Instanz IDisposable implementiert und Dispose() beim Herunterfahren aufruft, falls dies der Fall ist.

Die Verwaltung von Prototypen liegt jedoch vollständig in der Verantwortung des aufrufenden Codes.