如何在 .NET 的依赖注入配置中注册装饰器?

作者:微信公众号:【架构师老卢】
3-12 16:19
72

在本文中,我们将介绍几种不同的方法,以在 .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

⚠️ 但是,这种方法还有另一个与可读性相关的问题。这段代码有点麻烦。它的可读性不是很强,新的团队成员需要一些时间才能理解作者的意图。

可以使用以下方法解决此问题。

用 Scrutor 装饰

为了实现后一种方法,我们需要将库安装到我们的项目中,它为依赖注入提供了有用的扩展方法。它有一个扩展方法可以完成这项工作。ScrutorDecorate

配置如下所示:

services.AddScoped<IUsersRepository, UsersRepository>();  
services.Decorate<IUsersRepository, CachedUsersRepository>();

在第一行中,我们向实现注册接口。在第二行中,我们使用 Scrutor 扩展来装饰服务。IUsersRepositoryUsersRepositoryDecorateCachedUsersRepository

此方法非常适合表达意图,以便代码的所有读者都可以快速了解配置试图完成的任务。

在本文中,我们学习了 3 种使用软件开发中经常需要的包装器来装饰类的方法。

相关留言评论
昵称:
邮箱:
阅读排行