Twoje pytanie wraca do zasady własności:
kto ma prawo własności do zasobu, należy go wyrzucać.
Mimo że prawo własności można przenieść, zwykle nie należy tego robić. W twoim przypadku własność IDbConnection
została przeniesiona z ordersService
do OrdersRepository
(od OrdersRepository
zrzuca połączenie). Jednak w wielu przypadkach OrdersRepository
nie wie, czy połączenie może zostać usunięte. Może być ponownie wykorzystany na całym wykresie obiektu. Zasadniczo nie należy pozbywać się obiektów przekazywanych za pośrednictwem konstruktora.
Inną kwestią jest to, że konsument zależności często nie może wiedzieć, czy zależność wymaga utylizacji, ponieważ to, czy zależność musi zostać usunięta, jest szczegółem wdrożenia. Te informacje mogą być niedostępne w interfejsie.
Więc zamiast byłaby swój OrdersRepository
na następujące kwestie:
public class OrdersRepository : IOrdersRepository {
private IDbConnection _db;
public OrdersRepository(IDbConnection db) {
_db = db;
}
}
Od OrdersRepository
nie przejąć na własność, IDbConnection
nie trzeba wyrzucać IDbConnection
i nie trzeba wdrożyć IDisposable
.To wyraźnie przenosi odpowiedzialność za usunięcie połączenia z OrdersService
. Jednak sama nazwa ordersService
nie wymaga IDbConnection
jako zależności; to zależy od IOrdersRepository
. Dlaczego więc nie przenieść odpowiedzialność budowania wykresu obiektu z OrdersService
, a także:
public class OrdersService : IDisposable {
private readonly IOrdersRepository _orders;
public ordersService(IOrdersRepository ordersRepo) {
_orders = ordersRepo;
}
}
Od ordersService
ma nic do dysponowania się, że nie ma potrzeby, aby wdrożyć IDisposable
. A ponieważ ma teraz tylko jeden konstruktor, który wymaga zależności, klasa staje się łatwiejsza do utrzymania.
Przenosi to odpowiedzialność za tworzenie wykresu obiektu na OrdersController
. Ale powinniśmy zastosować ten sam wzór do OrdersController
, a także:
public class OrdersController : Controller {
private ordersService _orderService;
public OrdersController(ordersService o) {
_orderService = o;
}
}
Ponownie, ta klasa jest znacznie łatwiejsze do uchwycenia i nie potrzebuje do niczego wyrzucać, ponieważ nie ma lub przyjął własność dowolny zasób.
Oczywiście przenieśliśmy się i odłożyliśmy nasze problemy, ponieważ oczywiście nadal musimy stworzyć naszą OrdersController
. Ale różnica polega na tym, że przenieśliśmy teraz odpowiedzialność za tworzenie wykresów obiektów do pojedynczego miejsca w aplikacji. Nazywamy to miejsce Composition Root.
Zależność ramy wtryskowe może pomóc podejmowaniu głównej Skład utrzymaniu, ale nawet bez ramy DI, zbudować swój obiekt wykresu dość proste w MVC poprzez wdrożenie niestandardowych 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;
}
}
można podłączyć swój zwyczaj fabryka kontroler w Global asax swojej aplikacji MVC tak:
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();
}
}
oczywiście, to wszystko staje się dużo łatwiejsze, kiedy używasz ramy Dependency Injection. Na przykład, podczas korzystania z prostego Injector (jestem główną dev dla prostych Injector), można zastąpić wszystko z następujących kilku liniach kodu:
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));
}
}
Istnieje kilka ciekawych rzeczy dzieje się w powyższy kod. Przede wszystkim wywołania Register
mówią Simple Injector, że muszą zwrócić pewną implementację powinny zostać utworzone, gdy żądana jest abstrakcja. Następnie Simple Injector umożliwia rejestrowanie typów za pomocą Web Request Lifestyle
, co zapewnia, że dane wystąpienie zostanie usunięte po zakończeniu żądania WWW (tak jak w przypadku Application_EndRequest
). Dzwoniąc pod numer RegisterMvcControllers
, Simple Injector sprawdzi wszystkie kontrolery za Ciebie. Dostarczając MVC z SimpleInjectorDependencyResolver
, zezwalamy MVC na kierowanie tworzenia kontrolerów do Simple Injector (tak jak robiliśmy to z fabryką sterowników).
Chociaż ten kod może być nieco trudniejszy do zrozumienia na początku, użycie kontenera Dependency Injection staje się bardzo cenny, gdy aplikacja zaczyna się rozwijać. Pojemnik DI pomoże ci utrzymać porządek w strukturze składu.
Już zaglądałem do Finalizera i zauważyłem, że czasami nie wystrzeliło w moim obiekcie warstwy. Szukam bardziej niezawodnego rozwiązania, może za pomocą Ioc lub DI. ale nigdy wcześniej ich nie użyłem i nie wiem od czego zacząć. – highwingers
Jeśli to nie jest odpalanie, to twój obiekt nie jest czyszczony przez kolekcję śmieci i możesz mieć porę pamięci. Jeśli z założenia niektóre przypadki są utrzymywane na dłużej, być może powinieneś przemyśleć, jak ich używasz. –
Należy zaimplementować finalizator tylko w klasie, jeśli ta klasa musi bezpośrednio obsługiwać macierzyste zasoby. W tym przypadku implementacja finalizatora jest bezużyteczna, ponieważ samo 'DbConnection' ma już finalizator. – Steven