在本文中,我们将介绍几种不同的方法,以在 .NET 的依赖项注入配置中注册装饰器_(代理、包装器)。_
此外,我们还将了解每个选项的优缺点。
有时您可能希望在类之上实现包装器。这通常是为了扩展您正在包装的类的行为,并遵守开闭原则。例如,您有一个类:UsersRepository
public interface IUsersRepository
{
User GetUser(int userId);
}
public class UsersRepository : IUsersRepository
{
public User GetUser(int userId)
{
//Get a user from the database
return new User();
}
}
这就是依赖注入配置的样子:
services.AddScoped<IUsersRepository, UsersRepository>();
稍后,您可以决定通过实现代理来扩展 with 缓存行为。
下面是此类代理的实现如下所示:
public class CachedUsersRepository : IUsersRepository
{
private readonly IUsersRepository _usersRepository;
public CachedUsersRepository(IUsersRepository usersRepository)
{
_usersRepository = usersRepository;
}
public User GetUser(int userId)
{
//Caching logic here
return _usersRepository.GetUser(userId);
}
}
代理必须实现与主题类相同的接口。此外,代理将方法调用重定向到主题类,并在其上实现其他行为_(在我们的例子中为缓存)。_
如果我们需要开始使用而不是原始存储库,我们显然需要对依赖注入配置进行一些更改。如您所知,当前配置对代理一无所知。CachedUsersRepositoryCachedUsersRepository
services.AddScoped<IUsersRepository, UsersRepository>();
这似乎应该是一个简单的更改。我们可以这样稍微更新一下 DI 配置:
services.AddScoped<IUsersRepository, CachedUsersRepository>();
一切都应该起作用。但是,如果我们尝试运行应用程序,我们会得到一个异常,指出某些服务无法构造。
⚠️ 之所以会出现这个问题,是因为服务提供商无法解析我们引入的循环依赖关系。
因此,我们需要想出另一种方法来注册我们的代理。幸运的是,至少有 3 种方法可以解决这个问题。
对于此选项,我们需要在代理类中注入实现,而不是接口:UsersRepository
public class CachedUsersRepository : IUsersRepository
{
private readonly UsersRepository _usersRepository;
public CachedUsersRepository(UsersRepository usersRepository)
{
_usersRepository = usersRepository;
}
public User GetUser(int userId)
{
//Caching logic here
return _usersRepository.GetUser(userId);
}
}
接下来,在 DI 配置中,我们需要做两件简单的事情。第一个是注册 的实例,第二个是注册实现。下面是它的样子:UsersRepositoryIUsersRepositoryCachedUsersRepository
services.AddScoped<UsersRepository>();
services.AddScoped<IUsersRepository, CachedUsersRepository>();
就是这样。现在一切都会按预期工作。
⚠️ 但是,这种方法有一个缺点。代理依赖于类,而不是构造函数中的接口。但是,在某些情况下,代理必须仅依赖于接口以获得更大的灵活性。UsersRepositoryIUsersRepository
以下选项将有助于解决此问题。
因此,我们可以让我们的代理再次依赖于接口:
public class CachedUsersRepository : IUsersRepository
{
private readonly IUsersRepository _usersRepository;
public CachedUsersRepository(IUsersRepository usersRepository)
{
_usersRepository = usersRepository;
}
public User GetUser(int userId)
{
//Caching logic here
return _usersRepository.GetUser(userId);
}
}
然后在 DI 中,我们手动构造类型以避免循环依赖问题。CachedUsersRepository
以下是完整配置的样子:
services.AddScoped<UsersRepository>();
services.AddScoped<IUsersRepository, CachedUsersRepository>(provider => {
var userRepository = provider.GetRequiredService<UsersRepository>();
return new CachedUsersRepository(userRepository);
});
现在,服务提供商将在创建 时使用特定类型,因此将不再发生异常。UsersRepositoryCachedUsersRepository
⚠️ 但是,这种方法还有另一个与可读性相关的问题。这段代码有点麻烦。它的可读性不是很强,新的团队成员需要一些时间才能理解作者的意图。
可以使用以下方法解决此问题。
为了实现后一种方法,我们需要将库安装到我们的项目中,它为依赖注入提供了有用的扩展方法。它有一个扩展方法可以完成这项工作。ScrutorDecorate
配置如下所示:
services.AddScoped<IUsersRepository, UsersRepository>();
services.Decorate<IUsersRepository, CachedUsersRepository>();
在第一行中,我们向实现注册接口。在第二行中,我们使用 Scrutor 扩展来装饰服务。IUsersRepositoryUsersRepositoryDecorateCachedUsersRepository
此方法非常适合表达意图,以便代码的所有读者都可以快速了解配置试图完成的任务。
在本文中,我们学习了 3 种使用软件开发中经常需要的包装器来装饰类的方法。