У меня небольшая проблема, связанная с внедрением зависимостей. У меня есть программа, использующая уважительно современные правила внедрения зависимостей, использованные на примере проекта ASP.NET MVC и некоторых консольных программ.
Но он также содержит некий «шаблон поиска служб».
Я попытаюсь проиллюстрировать это очень простым консольным проектом:
using System;
using Autofac;
using System.Collections.Generic;
using System.Linq;
namespace AutofacIssue
{
class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterModule<MyModule>();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var service = scope.Resolve<UserService>();
var users = service.CreateSomeUsers();
var info = users.First().GetSomeInfo;
Console.WriteLine(info.Something);
}
}
}
internal class MyModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<UserService>();
builder.RegisterType<UserRelatedInfoService>();
}
}
internal class UserRelatedInfoService
{
public UserInfo GetForUser(int id)
{
return new UserInfo("Various infos for " + id);
}
}
internal class UserService
{
public IEnumerable<User> CreateSomeUsers()
{
return Enumerable.Range(1, 10).Select(r => new User(r)); // Remark "new User()" is called from many various spaces !
}
}
internal class UserInfo
{
// Some things
public string Something;
public UserInfo(string someThings)
{
this.Something = someThings;
}
}
/// <summary>
/// "Service locator" antipattern
/// </summary>
class DependencyLocator
{
public static IContainer Container { get; }
static DependencyLocator()
{
var builder = new ContainerBuilder();
builder.RegisterModule<MyModule>();
Container = builder.Build();
}
}
internal class User
{
public User(int id)
{
this.Id = id;
}
public int Id { get; private set; }
/// <summary>
/// Here's my problematic property using the DependencyLocator
/// </summary>
public UserInfo GetSomeInfo
{
get
{
UserRelatedInfoService userRelatedInfoService = DependencyLocator.Container.Resolve<UserRelatedInfoService>();
return userRelatedInfoService.GetForUser(Id);
}
}
}
}
Анти-паттерн позволяет писать очень маленький код, работающий безупречно, но нарушающий некоторые принципы DI (из-за «сервисного локатора» + дублированный контейнер, каждый из которых имеет свое время жизни).
Эта реализация также имеет преимущество в том, что создает экземпляр UserRelatedInfoService
только тогда, когда это действительно необходимо, если на самом деле вызывается связанное свойство User (имейте в виду, что пример из реального мира намного сложнее, и некоторые операции, связанные с этим может иметь стоимость)
В примере с реальным миром у меня такая ситуация во многих сборках, каждая из которых должна иметь возможность разрешать зависимости одним и тем же способом.
Мой вопрос таков: без изменения конструктора User
и конструкторов всех объектов, создающих некоторые User
, есть ли чистый способ избежать этого?
Каким-то «динамическим разрешением» зависимостей, например?
Обратите внимание, что User
не находится в той же сборке, что и мой класс Program
, поэтому я не могу получить доступ к исходному Container
как к общедоступному свойству.
Я решил, что одним из решений было сохранить класс DependencyLocator
, но удалить его содержимое и просто присвоить его свойство Container
тому, которое создано в main.
edit: К вашему сведению, пока я просто следовал своему собственному предложению и изменил DependencyLocator, чтобы он не перестраивал свой собственный контейнер, и просто установил на нем окончательный контейнер, созданный в точке входа приложения. Это было легко сделать, и это позволило избежать большинства проблем, указанных в первоначальном вопросе.
По крайней мере, код всегда будет использовать один и тот же контейнер.
Спасибо за чтение!