我们正在将庞大的代码库从.NET框架迁移到.NET核心。不幸的是,我们正在迁移的某些代码饱受设计异味的困扰,但是在此阶段我们不能随意破坏事物,因此我们需要仔细计划随时间的变化。
主要问题之一是我们的代码依赖于许多抽象工厂,这些工厂被称为a code smell(至少在被滥用时)。一个相关的问题是,Castle Windsor容器已在现有代码中广泛使用,我们希望避免在ASP.NET内核中使用它,而我们更喜欢使用默认的内置DI容器。
我试图了解是否有可能通过使用ASP.NET核心DI容器来重写当前基于城堡式Windsor容器的某些抽象工厂实现。根据我对依赖项注入的理解,在应用程序的组合根目录中编写此类类是一个问题。放置具有依赖于DI容器的成分根类的另一种方法是[[not引入service locator代码气味的方法。当前,我们抽象工厂的接口定义是leaky
,因为它公开了Release
方法,该方法只能遵守Castle Windsor的register resolve release pattern。这是当前的接口定义:public interface ICommandHandlerFactory
{
ICommandHandler CreateHandler(Type commandType);
void Release(ICommandHandler handler);
}
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); } }
我的问题与对象生存期管理有关。关键是,对于ASP.NET核心DI容器,生存期管理不是基于
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
方法来清楚地简化抽象工厂定义:
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;
}
}
}
你有什么想法吗?更新再次仔细阅读this article之后,我意识到解决此设计问题的方法非常明显。
整个讨论的重点是抽象工厂设计模式的内在问题,因此解决的方法可能是
完全避免抽象工厂]。
下面的代码是非常自动的解释,并显示了用另一个抽象(在示例中称为
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容器的代码的唯一模块)。我们正在将庞大的代码库从.NET框架迁移到.NET核心。不幸的是,我们正在迁移的某些代码遭受设计气味,但是在此阶段我们无法随意破坏东西,因此...简单来说,是工厂接管了DI容器。使用工厂时,工厂将管理其创建的事物的生命周期。您可以使用DI容器来管理factory的生存期,但这就是停止的地方。您的问题范围太广,无法在不为您重写代码的情况下合理回答。但是,从本质上讲,工厂应实施
IDisposable
。它应该更新它负责的事物,然后在Dispose
方法实现中将其处置。从Microsoft.Extensions.DependencyInjection
端,您将工厂注册为一个单例(以便它可以管理应用程序生存期的整个范围内的生存期,因为这一次,它再次接管了DI容器的角色)。然后,您可以将工厂注入任何需要的地方,并用它来创建所需的东西。