【问题标题】:Abstract Factory and Inversion of Control resolve at runtime抽象工厂和控制反转在运行时解决
【发布时间】:2015-05-15 02:17:39
【问题描述】:

我有以下类和接口结构,我很难让代码做我需要的事情。

public interface IUserManager
{
    int Add(User user);
}

public class UserManagerA : IUserManager{}
public class UserManagerB : IUserManager{}

在此示例中,我使用 Ninject 作为 IoC 容器,但如果其他容器解决了问题,我愿意更改它:

这是在我的NinjectWebCommon.cs

void RegisterServices(IKernel kernel)
{
    string userRole = CurrentUser.Role;//this gets the user logged in
    //This is the part I do not how to do
    //I wish I could just type this in:
    kernel.Bind<IUserManager>().To<UserManagerA>()
        .When(userRole == "RoleA"); // this doesn't work obviously
    kernel.Bind<IUserManager>().To<UserManagerB>()
        .When(userRole == "RoleB"); // same doesn't work
}

所有这些,以便在我的 (MVC) 控制器中我可以做到这一点:

public class UserController
{
    private readonly IUserManager _userManager;
    public UserController(IUserManager userManager)
    {
        _userManager = userManager;
    }
    public ActionResult Add(User user)
    {
        //this would call the correct manager
        //based on the userRole
        _userManager.Add(user);
    }
}

我一直在阅读有关抽象工厂的文章,但还没有找到一篇解释如何将工厂与 IoC 容器集成并传递在运行时获得的参数来解析实现的文章。

【问题讨论】:

  • .When(r =&gt; CurrentUser.Role == "RoleA"); ? - 假设 When 是根据您不想在设置期间捕获值的每个分辨率计算...
  • @AlexeiLevenkov 我不确定你的意思是什么? CurrentUser 是从登录的用户计算出来的,因此值会在每次请求时发生变化,Ninjects 绑定不是在每次创建控制器时都会发生吗?
  • 我不知道 Ninject 是如何工作的,我的建议是基于 Unity 的行为方式。我假设 Ninject 会在每次需要解析类型时执行 .When 并且默认解析频率(在 Unity 中也称为“生命周期”)是每次请求类型时......这样当控制器创建时(至少每个 http请求)它将得到IUserMamager匹配CurrentUser.Role....(可能完全关闭,因为我对Ninject没有直接的了解)
  • @AlexeiLevenkov 好的,谢谢,我会先尝试使用 ninject 如果它不起作用我会尝试统一。

标签: c# asp.net-mvc dependency-injection ioc-container abstract-factory


【解决方案1】:

创建一个负责提供正确UserManager 的类并将其注入您的控制器:

public class UserManagerProvider : IUserManagerProvider
{
    private readonly IContext _context;

    public UserManagerProvider(IContext context)
    {
        _context = context;
    }

    public IUserManager Create(User currentUser)
    {
        if (currentUser.Role == "User A")
            return _context.Kernel.Get<UserManagerA>();

        if (currentUser.Role == "User B")
            return _context.Kernel.Get<UserManagerB>();

        // Or bind and resolve by name
        // _context.Kernel.Get<IUserManager>(currentUser.Role);
    }
}

在控制器中:

private readonly IUserManager _userManager;

public UserController(IUserManagerProvider userManagerProvider)
{
    _userManager = userManagerProvider.Create(CurrentUser);
}

另外,作为旁注,您可能应该有一个 CurrentUserProvider 负责获取当前用户。依赖静态方法会使单元测试变得困难,并且您实际上是在所有引用它的类中隐藏了一个依赖项:

private readonly IUserManager _userManager;
private readonly User _currentUser;

public UserController(IUserManagerProvider userManagerProvider, ICurrentUserProvider currentUserProvider)
{
    _currentUser = currentUserProvider.GetUser();
    _userManager = userManagerProvider.Create(_currentUser);
}

【讨论】:

  • +1。我就是这样做的。如果您不想在组合根目录之外拥有内核引用,则您的 UserManagerProvider 实现可能是该项目的一部分(接口更广泛可用。此外,请考虑按名称注册和解析用户管理器,而不是使用 if提供者实现中的语句。
  • @PhilSandler 我同意,if 声明很讨厌。只是想让实施与提出的问题保持一致
  • @PhilSandler 您所说的注册和名称解析是什么意思?
  • @RagtimeWilly 我并没有真正为 CurrentUser 使用静态方法,这只是为了演示目的。我试图避免将角色传递给每个Manager 调用,因为一旦用户登录,我就已经知道应该使用哪些管理器。我要稍等一下,看看其他人是否出现了一个不需要在每次呼叫经理时都包含该角色的实现,如果不是,我将不得不采用您建议的这条路线.谢谢。
  • 按名称绑定和解析:github.com/ninject/ninject/wiki/…
【解决方案2】:

如果 IUserManager 实现的数量不是很多(不太可能达到 100 个实现),您可以使用 Strategy Pattern 在组合期间解析所有 UserManager 实例,然后选择最佳实例在运行时使用.

首先,我们需要一种将IUserManager 实现映射到角色的方法。

public interface IUserManager
{
    int Add(User user);
    bool AppliesTo(string userRole);
}

public class UserManagerA : IUserManager
{
    // Add method omitted

    public bool AppliesTo(string userRole)
    {
        // Note that it is entirely possible to 
        // make this work with multiple roles and/or
        // multiple conditions.
        return (userRole == "RoleA");
    }
}

public class UserManagerB : IUserManager
{
    // Add method omitted

    public bool AppliesTo(string userRole)
    {
        return (userRole == "RoleB");
    }
}

然后我们需要一个策略类,它可以根据userRole 简单地选择正确的实例。 IUserManager 实例由 DI 容器在组合应用程序时提供。

public interface IUserManagerStrategy
{
    IUserManager GetManager(string userRole);
}

public class UserManagerStrategy
    : IUserManagerStrategy
{
    private readonly IUserManager[] userManagers;

    public UserManagerStrategy(IUserManager[] userManagers)
    {
        if (userManagers == null)
            throw new ArgumentNullException("userManagers");

        this.userManagers = userManagers;
    }

    public IUserManager GetManager(string userRole)
    {
        var manager = this.userManagers.FirstOrDefault(x => x.AppliesTo(userRole));
        if (manager == null && !string.IsNullOrEmpty(userRole))
        {
            // Note that you could optionally specify a default value
            // here instead of throwing an exception.
            throw new Exception(string.Format("User Manager for {0} not found", userRole));
        }

        return manager;
    }
}

用法

public class SomeService : ISomeService
{
    private readonly IUserManagerStrategy userManagerStrategy;

    public SomeService(IUserManagerStrategy userManagerStrategy)
    {
        if (userManagerStrategy == null)
            throw new ArgumentNullException("userManagerStrategy");
        this.userManagerStrategy = userManagerStrategy;
    }

    public void DoSomething()
    {
        string userRole = CurrentUser.Role;//this gets the user logged in

        // Get the correct UserManger according to the role
        IUserManager userManager = this.userManagerStrategy.GetManger(userRole);

        // Do something with userManger
    }
}

void RegisterServices(IKernel kernel)
{
    kernel.Bind<IUserManager>().To<UserManagerA>();
    kernel.Bind<IUserManager>().To<UserManagerB>();

    // Ninject will automatically supply both IUserManager instances here
    kernel.Bind<IUserManagerStrategy>().To<UserManagerStrategy>();

    kernel.Bind<ISomeService>().To<SomeService>();
}

此方法不需要您将容器注入应用程序。没有使用服务位置。

还请注意,每次向应用程序添加新的UserManager 时,都不需要修改 switch case 语句。何时使用 UserManager 的逻辑是 UserManager 实现的一部分,执行逻辑的顺序由 DI 配置决定。

此外,无论您使用哪个 DI 容器,这都将起作用。

您可以将它与 RagtimeWilly 的答案中的 CurrentUserProvider 结合起来,以一种简洁的方式将用户角色放入使用它的服务中。

参考:Best way to use StructureMap to implement Strategy pattern

【讨论】:

    猜你喜欢
    • 2014-02-12
    • 2011-01-05
    • 1970-01-01
    • 2011-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-31
    • 1970-01-01
    相关资源
    最近更新 更多