依赖注入和工厂模式

6
有一个比较好的方式来使用DI方法处理条件子工厂。场景是注入到entry中的loader对象取决于该entry的设置。最初我是将IoC容器注入到工厂中,并根据命名约定进行解析。然而,我真的很想保持工厂干净而不涉及容器。
工厂被注入到一个类中,该类将从数据库加载所有设置,然后使用工厂创建一组entry。设置确定在给定entry内部将使用哪个loader。
编辑:更改代码以更好地突出实际问题。问题在于必须同时支持多个数据库管理器,如果不是这种情况,那就很简单了。数据库管理器类型由存储在特定entry的设置确定。
public class Entry : IEntry
{
     private ISomething loader;

     public Entry(ISomething something)
     {
         this.loader = something;
     }
}

public class EntryFactory : IEntryFactory
{
    IEntry BuildEntry(IEntrySetting setting)
    {
        //Use setting object to determine which database manager will be used
    }
}

public class EntryManager
{
    public EntryManager(IEntryFactory entryFactory)
    {
        var entrySettings = this.settings.Load();
        foreach(var setting in entrySettings)
        {
             this.entries.Add(entryFactory.BuildEntry(setting));
        }
    }
}

我曾考虑将子工厂注册到主工厂并以这种方式解决它们,但我不知道是否有更好的方法。


1
你在依赖解析机制中有一个运行时输入,因此我怀疑在这里实现完全的容器独立性是不可能的。因此,将其推迟到工厂似乎是你能做的最好的事情。你也可以发现Autofac的关系类型很有趣。 - default.kramer
我看到你在入口构造函数中做了一些工作,这就是为什么整个 DI 和工厂的东西都不起作用的问题所在。 loader.Load();让构造函数只包含字段初始化。load 方法是做什么的?它是否会将数据填充到 entry 对象中?如果是这种情况,那么这不是构造函数的工作。工厂必须将准备好的加载数据放入 entry 中,并从其他地方获取它。 - George Mamaladze
2个回答

1
通常我会为我的 DI 容器创建一个包装器,例如 IDependencyResolver,并将其注入到我的工厂中。然后,您可以拥有一个类似于 StructureMapDependencyResolver 的实现来完成这项工作。我喜欢这种方式胜过直接注入容器本身,因为这样可以让我自由地更改 DI 容器(几乎)立即生效。至少我的工厂不需要改变。
public interface IDependencyResolver
{
    T Resolve<T>();
}

public class UnityDependencyResolver : IDependencyResolver
{
    private readonly IUnityContainer _container;

    public UnityDependencyResolver(IUnityContainer container)
    {
        _container = container;
    }

    public T Resolve<T>()
    {
        return _container.Resolve<T>();
    }
}

这种方法非常灵活,您可以实现自己的依赖项解析器并手动注入它们。

public class ManualDependencyResolver : IDependencyResolver
{
    public T Resolve<T>()
    {
        if (typeof(T)==typeof(ITransactionRepository))
        {
            return new CheckTransactionRespostory(new DataContext());
        }

        throw new Exception("No dependencies were found for the given type.");
    }
}

4
这里比较困难的地方是它引入了一些“服务定位器模式”的概念,这在工厂中或许没什么问题,但是如果我需要/想要切换到无容器模式,那么它就会变得更加困难并且隐藏了依赖关系。 - crb04c
很有道理。我不介意在工厂中使用服务定位器。但是,如果您想完全删除容器,您可以实现自己的依赖关系解析器,将类依赖性硬编码或甚至可通过配置文件进行配置。当我输入这些时,我会感到有点不舒服,但是如果容器引起问题,您从技术上讲确实可以这样做。 - Byron Sommardahl
传递 DI 容器往往是一种反模式。容器本身就是一个工厂。通过使用包装器减少了痛苦,但我认为这不是最佳解决方案。 - TrueWill

0

这取决于你的 DI 框架允许什么,而你没有具体说明。使用 Autofac 基于委托的注册,我得出了以下解决方案。请注意,在两种情况下,ILoaderFactoryIEntryFactory 都被简单的 Func<> 工厂所替代。

解决方案 1,使用两个工厂:

public class EntryManager
{
    public EntryManager(Func<ILoader, IEntry> entryFactory, Func<Settings, ILoader> loaderFactory)
    {
        var entrySettings = this.settings.Load();
        foreach(var setting in entrySettings)
        {
            this.entries.Add(entryFactory(loaderFactory(setting)));
        }
    }
}

private static ILoader SelectLoader(IEntrySetting settings)
{
    // your custom loader selection logic
}

var builder = new ContainerBuilder();
builder.RegisterType<EntryManager>();
builder.RegisterType<Entry>().As<IEntry>();
builder.Register((c, p) => SelectLoader(p.TypedAs<IEntrySetting>()));
IContainer container = builder.Build();
container.Resolve<EntryManager>();

解决方案2,只使用一个工厂:

public class EntryManager
{
    public EntryManager(Func<IEntrySetting, IEntry> entryFactory)
    {
        var entrySettings = this.settings.Load();
        foreach(var setting in entrySettings)
        {
            this.entries.Add(entryFactory(setting));
        }
    }
}

private static ILoader SelectLoader(IEntrySetting settings)
{
    // your custom loader selection logic
}

var builder = new ContainerBuilder();
builder.RegisterType<EntryManager>();
builder.Register((c, p) => new Entry(SelectLoader(p.TypedAs<IEntrySetting>()))).As<IEntry>();
IContainer container = builder.Build();
container.Resolve<EntryManager>();

我质疑的是选择逻辑部分,我曾经让容器根据设置解析具有命名注册的类型来解决问题。问题是有什么好的方法可以将工厂与容器的显式知识分离开来。 - crb04c
Unity 也支持自动工厂。 - TrueWill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接