【问题标题】:Dependency Injection in Transient Objects瞬态对象中的依赖注入
【发布时间】:2019-07-15 23:27:25
【问题描述】:

我想要一些关于如何通过依赖注入构造一些对象的建议。

我的大部分应用程序都是单例,将单例作为相互依赖注入非常简单。

但是,我有一种情况,我会动态生成一些依赖于多个单例的瞬态对象。

这是当前情况的一些 C# 伪代码:

// random singletons
public static class SingletonA {...}
public static class SingletonB {...}
public static class SingletonC {...}

// random objects that all need to depend on some subset of the above singletons
public class BaseTask {...}
public class TaskA : BaseTask {...}
public class TaskB : BaseTask {...}
public class TaskC : BaseTask {...}

public class Scheduler {
    public Scheduler() {
    }

    public void Start() {
        // When these tasks are created is actually really dependent on business logic,
        // and each task executes a bunch of internal logic.
        // Each Task can create more children Task at arbitrary times too.
        ...
        var taskA = new TaskA();
        ...
        var taskB = new TaskB();
        ...
        var taskC = new TaskC();
        ...
}

TaskA, TaskB, TaskC, ... 中都有调用单例方法的代码。此外,每个任务都可以构建新任务。

如果我使用依赖注入,我可以这样做:

public class Scheduler {
    private ISingletonA _singletonA;
    private ISingletonB _singletonB;
    ...

    public Scheduler(ISingletonA singletonA, ISingletonB singletonB, ...) {
        _singletonA = singletonA;
        _singletonB = singletonB;
        ...
    }

    public void Start() {
        ...
        var taskA = new TaskA(_singletonA, _singletonB, ...);
        ...
        var taskB = new TaskB(_singletonA, _singletonB, ...);
        ...
        var taskC = new TaskC(_singletonA, _singletonB, ...);
        ...
    }
}

这看起来很乱,所以我正在考虑将所有 TaskATaskBTaskC 重构为一个通用类并制作类似工厂的东西:

public class Scheduler {
    public TaskFactory _taskFactory

    public Scheduler(TaskFactory taskFactory) {
        _taskFactory = taskFactory;
    }

    public void Start() {
        ...
        var taskA = _taskFactory.NewTaskA(_taskFactory);
        ...
        var taskB = _taskFactory.NewTaskB(_taskFactory);
        ...
        var taskC = _taskFactory.NewTaskC(_taskFactory);
        ...
    }
}

有更好的想法吗?如果不是,我认为这不是工厂模式。有更准确的名称吗?

【问题讨论】:

  • 您将单例显示为静态类。这是故意的吗?
  • 是的,使用工厂模式是处理瞬态对象需要注入依赖项的情况的第一个也是最明显的选项。工厂负责实例化和填充构造函数参数,只需将自己注入的依赖项复制到构造函数参数中即可。只要确保给工厂一个接口,以便单元测试可以存根它。
  • @Nkosi 它们目前是静态类。当我引入依赖注入时,它们不会是静态类。
  • @JohnWu 我认为工厂通常有一个创建方法来创建各种对象。就我而言,我的工厂接口将有 3 次以上的创建(基本上将所有构造函数整理到一个类中)。那仍然被认为是“工厂”吗?不幸的是,我还需要将工厂作为依赖项传递给每个 Task 类。
  • @JohnWu 我说的是语义上的Factory

标签: c# unit-testing design-patterns factory factory-pattern


【解决方案1】:

我将定义一个工厂类,其唯一目的是构造您的 TaskX 对象,包括包含所有依赖项:

class MyTaskFactory : IMyTaskFactory
{
    private readonly ISingletonA _singletonA;
    private readonly ISingletonB _singletonB;

    public MyTaskFactory(ISingletonA singletonA, ISingletonB singletonB)
    {
        _singletonA = singletonA;
        _singletonB = singletonB;
    }

    public T Resolve<T>() where T : ITask
    {
        if (typeof(T) == typeof(TaskA)) return (T)(object)GetTaskA();
        if (typeof(T) == typeof(TaskB)) return (T)(object)GetTaskB();
        if (typeof(T) == typeof(TaskC)) return (T)(object)GetTaskC();

        throw new ArgumentException(string.Format("Type not supported: {0}", typeof(T).FullName));
    }

    protected TaskA GetTaskA()
    {
        return new TaskA(_singletonA);
    }

    protected TaskB GetTaskB()
    {
        return new TaskB(_singletonA, _singletonB);
    }

    protected TaskC GetTaskC()
    {
        return new TaskC(_singletonA, "Other runtime parameter");
    }
}

public class Scheduler
{
    protected readonly IMyTaskFactory _taskFactory;

    public Scheduler(IMyTaskFactory taskFactory)
    {
        _taskFactory = taskFactory;
    }

    public void Start()
    {
        var taskA = _taskFactory.Resolve<TaskA>();
        var taskB = _taskFactory.Resolve<TaskB>();
        var taskC = _taskFactory.Resolve<TaskC>();
    }
}

然后您将工厂添加到您的组合根目录中:

container.Register<IMyTaskFactory,MyTaskFactory>();

并且依赖项会显示在需要它们的地方。

单击here 获取包含可编译代码的 Fiddle。

【讨论】:

  • 我的任务实例化实际上更加复杂。每个任务都依赖于一组不同的单例以及原语(如字符串、整数等)。因此,各种构造函数非常不同,很难概括。在这种情况下,最好只是“违反”传统的“工厂”模式,在工厂中拥有多种创建方法。这种风格有名字吗?
  • 请记住,单元测试人员将不得不对此进行模拟,并且他们更容易编写单个模拟 Resolve&lt;T&gt; 语句。但你不必那样做才能成为工厂。当您对不同类型有单独的方法时,我不知道它有什么特殊名称。
  • 我同意你的观点,即单元测试人员必须模拟它,但鉴于构造函数参数的差异,将它们塞进一个通用方法似乎太难了。您建议如何实现这一目标?
  • 我倾向于同意。如果逻辑不是通用的,则通用方法是不合适的。您可以使用单独的方法。如果需要,您还可以使用一系列简单(虽然看起来很傻)if 语句来实现两者(请参阅我更新的示例)。
  • 谢谢。我可能会使用单独的方法,并将工厂传递给每个Task,因为Task 也可以实例化一个子Task
【解决方案2】:

如果我错了,请纠正我,但我认为你所做的类似于抽象工厂模式。

我不确定您希望应用程序可配置多深。我建议根据您当前的结构并根据您展示的内容创建一个应用程序文件,您可以在其中混合和匹配注射剂。

    {
        "Schedulers": [
            {
                "id": "A",
                "type": "....",
                "tasks": "tA, tB"
            },
            {
                "id": "B",
                "type": "....",
                "tasks": "tC"
            }
        ],
        "Tasks": [
            {
                "id": "tA",
                "type": "..."
            },
            {
                "id": "tB",
                "type": "..."
            },
            {
                "id": "tC",
                "type": "..."
            }
        ]
    }

基本思想是您可以将“任务”分配给您的“调度程序”。

但是,为了做到这一点,您需要使用另一个 IoC 容器,例如 Autofac,因为默认的 ASP.NET Core DI 容器使得这有点不可能实现。

【讨论】:

    猜你喜欢
    • 2021-01-14
    • 1970-01-01
    • 2015-01-22
    • 1970-01-01
    • 2023-04-09
    • 2013-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多