【问题标题】:Is the enum class pattern incompatible with DI?枚举类模式是否与 DI 不兼容?
【发布时间】:2013-12-01 12:04:42
【问题描述】:

在进入 DI 之前,我非常喜欢使用所谓的枚举类(或我脑海中的强枚举),其中枚举被转换为类,但设置为以与枚举类似的方式使用。这使得真正属于特定枚举的逻辑能够被封装在正确的位置,并防止代码库中出现大量混乱。

一个例子是在这里找到的http://lostechies.com/jimmybogard/2008/08/12/enumeration-classes/

一旦将 DI 带入方程,由于依赖静态变量,它会出现问题。

有什么方法可以继续支持这种模式并同时使用 DI?

编辑: 如果我想向 EmployeeType 注入一些新的东西,这是一个有问题的类型示例。由于静态变量,我无法使用容器。

public class EmployeeType : Enumeration
{
    public static readonly EmployeeType Manager 
        = new ManagerType (0, "Manager");
    public static readonly EmployeeType Servant 
        = new EmployeeType(1, "Servant");
    public static readonly EmployeeType AssistantToTheRegionalManager 
        = new EmployeeType(2, "Assistant to the Regional Manager");

    private EmployeeType() { }
    private EmployeeType(int value, string displayName) : base(value, displayName) { }
}

public class ManagerType : EmployeeType
{
}

【问题讨论】:

  • 这种模式相对于常规继承或策略模式(两者都没有这个问题)有什么优势?
  • 我认为熟悉度/可用性以及从枚举到枚举类的迁移容易(即使用相同)。
  • 请举例说明您正在做什么以及与 DI 一起出现问题的地方。
  • 当它本质上是硬编码时,为什么你需要 DI 呢?您不会将 DI 与标准枚举一起使用,对吗?
  • 这不是我的实际代码,但坚持这个假例子,假设我后来想注入一些工厂或其他,因为我需要在 EmployeeType 接口中公开一些东西。

标签: c# .net dependency-injection ioc-container simple-injector


【解决方案1】:

我想你可以在这里使用Strategy pattern。您可以保留枚举类型或使用任何类型来帮助您识别策略(我将在示例中使用字符串)。然后为每个值实施一个策略。

interface IEmployeeHandler
{
    string EmployeeType { get; }
    void Handle(Employee employee);
}

class ManagerHandler : IEmployeeHandler
{
    public ManagerHandler()
    {
        EmployeeType = "Manager";
    }

    public string EmployeeType { get; private set; }
    public void Handle(Employee employee) { }
}

class ServantHandler : IEmployeeHandler
{
    public ServantHandler()
    {
        EmployeeType = "Servant";
    }

    public string EmployeeType { get; private set; }
    public void Handle(Employee employee) { }
}

由于 DI 容器可以注入接口的多个实现,您可以像这样编写策略提供程序类:

class EmployeeStrategyProvider
{
    private readonly IEmployeeHandler[] _employeeHandlers;

    public EmployeeStrategyProvider(IEmployeeHandler[] employeeHandlers)
    {
        _employeeHandlers = employeeHandlers;
    }

    public IEmployeeHandler GetStrategy(string employeeType)
    {
        return _employeeHandlers.FirstOrDefault(item => item.EmployeeType == employeeType);
    }
}

您可以使用此类根据员工类型获取正确的策略。这就是我使用 DI 容器实现策略模式的方式。它可以使用一些改进,但我 很高兴听到一些建议。

【讨论】:

  • 有没有办法仍然保留 EmployeeType.Manager 枚举样式表示法。您是否曾经将上述内容与单身人士结合起来(参见 B3ret 的回答)?这是个好主意吗?
  • @Ian1971 可以,但我认为没必要。您仍然需要使用代码中的一些数据创建该“枚举类型”的实例。只需将这些数据包含在您的策略界面中,您就可以在没有所有这些静态实例的情况下使其工作。
  • 好的,有道理。我喜欢你的实现。所以我想也许我的问题的答案是“枚举类模式与 di 不兼容吗?”确实不行,不过还是换成策略模式比较好。
  • @Ian1971 我不能说它不兼容。他们当然可以一起工作,但我认为这是多余的。虽然我在这里没有权威,但您可以为您的案件做出最好的决定。
【解决方案2】:

我自己使用这种模式已经有一段时间了,但从未考虑过需要向其中注入服务。但是,嘿,这应该是可能的吧?

一种解决方案是将依赖于服务实例的任何枚举的创建延迟到需要它们之前 - 其他一切看起来和行为都像常规静态类型。

这是 EmployeeType 类,添加了依赖于 IBonusServiceManager 的延迟实例化:

public class EmployeeType : Enumeration
{
    public static Func<IBonusService> BonusService { private get; set; }

    private static EmployeeType _manager = null;
    public static EmployeeType Manager { 
        get 
        {
            if (_manager == null) _manager = new ManagerType(BonusService());
            return _manager;
        } }

    public static readonly EmployeeType Servant
        = new EmployeeType(1, "Servant");
    public static readonly EmployeeType AssistantToTheRegionalManager
        = new EmployeeType(2, "Assistant to the Regional Manager");
    private EmployeeType(int value, string displayName) :
       base(value, displayName) { }

    public virtual decimal BonusSize { get { return 0; } }

    private class ManagerType : EmployeeType
    {
        private readonly IBonusService service;
        public ManagerType(IBonusService service) : base(0, "Manager")
        {
            this.service = service;
        }

        public override decimal BonusSize {
            get { return this.service.currentManagerBonus; } }
    }
}

然后您在组合根目录中配置依赖项:

[Test]
public void EmployeeType_Manager_HasBonusService()
{
    Container container = new Container();
    container.Register<IBonusService, BonusServiceStub>();
    EmployeeType.BonusService = () => container.GetInstance<IBonusService>();

    BonusServiceStub.constructed = false;
    container.Verify();
    //Verify has ensured the container can create instances of IBonusService
    Assert.That(BonusServiceStub.constructed, Is.True);

    EmployeeType manager = EmployeeType.Manager;
    Assert.That(manager.BonusSize == 999m);
}


public class BonusServiceStub : IBonusService
{
    public static bool constructed = false;

    public BonusServiceStub() { constructed = true; }

    public decimal currentManagerBonus { get { return 999m; } }
}

专门注入 Func&lt;IBonusService&gt; 而不是 IBonusService 的原因是,服务的生命周期范围(以及装饰等)在组合根中进行管理,并且独立于使用者。

【讨论】:

  • 这很好。我想知道的一件事是 EmployeeType.BonusService = () => container.GetInstance();使其成为服务定位器(即反模式)还是可以,因为 Func 的定义在组合根中,即使它可能稍后被实例化?
  • @Ian1971 所有配置都在组合根目录中,因此它不是服务定位器。如果您没有正确配置EmployeeType,您将在尝试获取Manager 时收到NullReference 错误——该错误位于组合根目录中。发布后我突然想到,您可以为每次通话获得一个新的 Manager 实例,并且根本不会缓存 - 确保您始终获得当前的奖金金额。
【解决方案3】:

你可以为你的枚举类使用单例。

【讨论】:

  • 这确实发生在我身上,但我不清楚如何创建该单例并注入依赖项,但考虑一下我想如果单例是在 CompositionRoot 中创建的它可能工作正常。
  • 我想如果枚举类的每个子类型有的话,我需要一个单例。例如上面的ManagerType
  • 是的,很遗憾,您必须为每个子类实现单例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多