Ihre Frage kommt zu dem Grundsatz der Eigenverantwortung zurück:
wer das Eigentum an der Ressource hat, sollte es entsorgen.
Obwohl Eigentumsrechte übertragen werden können, sollten Sie dies normalerweise nicht tun. In Ihrem Fall wird der Besitz des IDbConnection
von ordersService
auf OrdersRepository
übertragen (seit OrdersRepository
verfügt über die Verbindung). Aber in vielen Fällen kann der OrdersRepository
nicht wissen, ob die Verbindung entsorgt werden kann. Es kann im gesamten Objektdiagramm wiederverwendet werden. Im Allgemeinen sollten Sie Objekte, die über den Konstruktor an Sie übergeben werden, nicht entfernen.
Eine andere Sache ist, dass der Verbraucher einer Abhängigkeit oft nicht wissen kann, ob eine Abhängigkeit entsorgt werden muss, denn ob eine Abhängigkeit entsorgt werden muss, ist ein Implementierungsdetail. Diese Information ist möglicherweise in der Schnittstelle nicht verfügbar.
Also statt, Refactoring Ihre OrdersRepository
auf die folgenden:
public class OrdersRepository : IOrdersRepository {
private IDbConnection _db;
public OrdersRepository(IDbConnection db) {
_db = db;
}
}
Da OrdersRepository
nicht Besitz nimmt, IDbConnection
braucht nicht IDbConnection
zu verfügen und Sie müssen nicht IDisposable
implementieren.Dies verschiebt explizit die Verantwortung für die Entsorgung der Verbindung zu OrdersService
. Jedoch benötigt ordersService
an sich nicht IDbConnection
als eine Abhängigkeit; Es kommt nur auf IOrdersRepository
. Warum also nicht bewegen, um die Verantwortung der Aufbau des Objektgraphen aus dem OrdersService
auch:
public class OrdersService : IDisposable {
private readonly IOrdersRepository _orders;
public ordersService(IOrdersRepository ordersRepo) {
_orders = ordersRepo;
}
}
Da ordersService
nichts selbst zu entsorgen hat, gibt es keine Notwendigkeit, IDisposable
zu implementieren. Und da es jetzt nur noch einen Konstruktor gibt, der die erforderlichen Abhängigkeiten übernimmt, ist die Verwaltung der Klasse viel einfacher geworden.
So verschiebt dies die Verantwortung der Erstellung des Objektdiagramms auf die OrdersController
. Aber wir sollten das gleiche Muster auf die OrdersController
gelten auch:
public class OrdersController : Controller {
private ordersService _orderService;
public OrdersController(ordersService o) {
_orderService = o;
}
}
Auch dieser Klasse viel leichter zu erfassen und es tut braucht nichts zu entsorgen, da tut es nicht hat oder nahm das Eigentum an jede Ressource.
Natürlich sind wir gerade umgezogen und haben unsere Probleme verschoben, da wir natürlich noch unsere OrdersController
erstellen müssen. Der Unterschied besteht jedoch darin, dass wir jetzt die Verantwortung für den Aufbau von Objektdiagrammen an einen einzigen Ort in der Anwendung verlagert haben. Wir nennen diesen Ort Composition Root.
Dependency Injection-Frameworks können Ihnen helfen, Ihre Zusammensetzung Wurzel wartbar zu machen, aber auch ohne DI Rahmen, können Sie Ihre Objektgraph ganz einfach in MVC erstellen, indem Sie eine benutzerdefinierte Implementierung ControllerFactory
:
public class CompositionRoot : DefaultControllerFactory {
protected override IController GetControllerInstance(
RequestContext requestContext, Type controllerType) {
if (controllerType == typeof(OrdersController)) {
var connection = new Ajx.Dal.DapperConnection().getConnection();
return new OrdersController(
new OrdersService(
new OrdersRepository(
Disposable(connection))));
}
else if (...) {
// other controller here.
}
else {
return base.GetControllerInstance(requestContext, controllerType);
}
}
public static void CleanUpRequest() }
var items = (List<IDisposable>)HttpContext.Current.Items["resources"];
if (items != null) items.ForEach(item => item.Dispose());
}
private static T Disposable<T>(T instance)
where T : IDisposable {
var items = (List<IDisposable>)HttpContext.Current.Items["resources"];
if (items == null) {
HttpContext.Current.Items["resources"] =
items = new List<IDisposable>();
}
items.Add(instance);
return instance;
}
}
Sie Ihre benutzerdefinierten Haken können Controller Factory in der globalen asax Ihrer MVC-Anwendung wie folgt aus:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
ControllerBuilder.Current.SetControllerFactory(
new CompositionRoot());
}
protected void Application_EndRequest(object sender, EventArgs e)
{
CompositionRoot.CleanUpRequest();
}
}
natürlich ist dies alles wird viel einfacher, wenn Sie ein Dependency Injection-Framework verwenden. Zum Beispiel, wenn Sie einfachen Injector verwenden (Ich bin die Führung Entwickler für Simple Injector), können Sie all dies mit den folgenden paar Zeilen Code ersetzen:
using SimpleInjector;
using SimpleInjector.Integration.Web;
using SimpleInjector.Integration.Web.Mvc;
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var container = new Container();
container.RegisterPerWebRequest<IDbConnection>(() =>
new Ajx.Dal.DapperConnection().getConnection());
container.Register<IOrdersRepository, OrdersRepository>();
container.Register<IOrdersService, OrdersService>();
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.Verify();
DependencyResolver.SetResolver(
new SimpleInjectorDependencyResolver(container));
}
}
Es gibt ein paar interessanten Dinge, die sie in der Code oben. Zuerst, die Aufrufe an Register
sagen Simple Injector, dass sie eine bestimmte Implementierung zurückgeben müssen, sollte erstellt werden, wenn die angegebene Abstraktion angefordert wird. Als nächstes erlaubt Simple Injector das Registrieren von Typen mit der Web Request Lifestyle
, die sicherstellt, dass die gegebene Instanz bei Beendigung der Web-Anfrage entsorgt wird (genau wie wir es in der Application_EndRequest
getan haben). Mit dem Aufruf RegisterMvcControllers
registriert Simple Injector alle Controller für Sie. Indem wir MVC mit der SimpleInjectorDependencyResolver
versorgen, erlauben wir MVC, die Erstellung von Steuerungen an den einfachen Injektor zu leiten (genau wie wir es mit der Fabrik der Steuerung gemacht haben).
Obwohl dieser Code auf den ersten Blick etwas schwieriger zu verstehen ist, wird die Verwendung eines Dependency Injection-Containers sehr nützlich, wenn Ihre Anwendung zu wachsen beginnt. Ein DI-Container wird Ihnen helfen, Ihre Composition Root wartbar zu halten.
Ich schaute bereits in Finalizer, und ich bemerkte, dass es manchmal nicht innerhalb meines Service-Layer-Objekts ausgelöst wurde. Ich suche nach einer robusteren Lösung, vielleicht mit Ioc oder DI. aber ich habe sie vorher nie benutzt und weiß nicht wo ich anfangen soll. – highwingers
Wenn es nicht ausgelöst wird, wird Ihr Objekt nicht von der Speicherbereinigung bereinigt, und Sie haben möglicherweise einen Speicher-Lauch. Wenn durch Design einige Instanzen für eine lange Suration gehalten werden, dann sollten Sie vielleicht darüber nachdenken, wie Sie sie verwenden. –
Sie sollten nur einen Finalizer in einer Klasse implementieren, wenn diese Klasse direkt mit nativen Ressourcen umgehen muss. In diesem Fall ist die Implementierung eines Finalizers nutzlos, da 'DbConnection' selbst bereits einen Finalizer besitzt. – Steven