Мы переносим огромную базу кода с .NET Framework на .NET core. К сожалению, часть кода, который мы переносим, страдает запахами дизайна, но мы не можем свободно ломать вещи на этом этапе, и нам нужно тщательно планировать изменения во времени.
Одна из основных проблем заключается в том, что наш код опирается на большое количество абстрактных фабрик, которые, как известно, являются запахом кода (по крайней мере, при злоупотреблении). Одна связанная с этим проблема заключается в том, что контейнер Castle Windsor широко используется в существующем коде, и мы хотели бы избегать его использования внутри ядра ASP.NET, где мы предпочитаем придерживаться встроенного контейнера DI по умолчанию.
I 'Я пытаюсь понять, возможно ли переписать некоторые из наших абстрактных реализаций фабрики, в настоящее время основанные на контейнере Castle Windsor, с помощью основного DI-контейнера ASP.NET. Исходя из моего понимания внедрения зависимостей, написание таких классов в корне композиции приложения является , а не проблемой;другими словами, класс корня композиции, который зависит от контейнера DI, - это , а не способ представить указатель службы запах кода.
В настоящее время наше определение интерфейсанашей абстрактной фабрики является дырявая , потому что она предоставляет метод Release
, который используется только для того, чтобы придерживаться шаблона выпуска разрешения 1011 * регистра замка Виндзора . Это текущее определение интерфейса:
public interface ICommandHandlerFactory
{
ICommandHandler CreateHandler(Type commandType);
void Release(ICommandHandler handler);
}
Конкретная реализация зависит от контейнера Castle Windsor, чтобы разрешить обработчики команд и освободить их, когда пользователь завершит:
public class WindsorCommandHandlerFactory
{
private readonly IKernel _container;
public WindsorCommandHandlerFactory(IKernel container)
{
_container = container;
}
public ICommandHandler CreateHandler(Type commandType)
{
// here we create the command handler type from the command type and then
// we ask the container to resolve the command handler type
}
public void Release(ICommandHandler handler)
{
_container.ReleaseComponent(handler);
}
}
Мой вопрос связан с управлением жизненным циклом объекта. Дело в том, что в базовом DI-контейнере ASP.NET управление временем жизни не основано на методе Release
, а основано на концепции scope .
. в основном создает область из корневого контейнера приложения, разрешает зависимости из контейнера области, использует зависимости и, наконец, удаляет область. Когда область действия удаляется, области и временные службы, разрешенные в области действия, выводятся из эксплуатации, и утечки памяти избегаются.
Это соответствующий код:
public class Worker
{
private readonly IServiceProvider container;
public Worker(IServiceProvider container)
{
_container = container;
}
public void DoStuff()
{
using(var scope = container.CreateScope())
{
var service = scope.ServiceProvider.GetRequiredService<IService>();
service.Work();
}
}
}
Чтобы придерживаться этого дизайна, я должен четко упростить абстрактное фабричное определение, удалив утечки (и теперь не более полезные) Release
method:
public interface ICommandHandlerFactory
{
ICommandHandler CreateHandler(Type commandType);
}
Учитывая это определение интерфейса, как я могу справиться с созданием и удалением областей контейнера?
Я не могу просто сделать следующее, потому что при удалении области действия разрешенные зависимости выводятся из эксплуатации, и поэтому вызывающий код потенциально может иметь ссылку на удаленный объект:
// this WON'T work due to the scope disposal when the service is returned
public class CommandHandlerFactory
{
private readonly IServiceProvider container;
public CommandHandlerFactory(IServiceProvider container)
{
_container = container;
}
public ICommandHandler CreateHandler(Type commandType)
{
using(var scope = container.CreateScope())
{
Type commandHandlerType = ... // build the command handler type starting from the command type
var service = scope.ServiceProvider.GetRequiredService(commandHandlerType);
return service;
}
}
}
У вас есть идеи?
Обновление
После тщательного прочтения этой статьи я снова понял, что решениеэта проблема дизайна довольно очевидна.
Главной темой обсуждения являются внутренние проблемы с шаблоном абстрактного проектирования фабрики, поэтому решение, вероятно, избегает абстрактной фабрики вообще .
Следующий код довольно автоматически объясняет и показывает способ замены абстрактной фабрики другой абстракцией (в нашем примере это называется ICommandDispatcher
), которая в основном является адаптером, используемым для вызова правильного обработчика команды для командыобрабатываться.
public interface ICommand
{
}
public interface ICommandHandler<T> where T : ICommand
{
void Handle(T command);
}
public interface ICommandDispatcher
{
void Dispatch(ICommand command);
}
public class CommandDispatcher : ICommandDispatcher
{
private readonly IServiceProvider _container;
public CommandDispatcher(IServiceProvider container)
{
_container = container ?? throw new ArgumentNullException(nameof(container));
}
public void Dispatch(ICommand command)
{
if (command == null)
throw new ArgumentNullException(nameof(command));
var commandType = command.GetType();
var commandHandlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
using (var scope = _container.CreateScope())
{
var commandHandler = scope.ServiceProvider.GetRequiredService(commandHandlerType);
((dynamic)commandHandler).Handle((dynamic)command);
}
}
}
Теперь весь код, ранее зависящий от ICommandHandlerFactory
, должен зависеть от новой абстракции ICommandDispatcher
. Просто не забудьте определить класс CommandDispatcher
в корне композиции вашего приложения (корень композиции является единственным модулем кода, который может зависеть от контейнера IOC).