【问题标题】:C# - use dependency injection (ninject) instead of factory patternC# - 使用依赖注入(ninject)而不是工厂模式
【发布时间】:2016-04-26 10:29:03
【问题描述】:

我已经阅读了很多关于这个主题的内容,但无法完全掌握。

我正在尝试使用 Ninject.Extensions.Factory 而不是我的工厂来根据用户输入创建新对象。我想充分利用 Ninject 功能和 IoC 概念。

现在代码如下所示:

interface IFeatureFactory
{
    IFeature createFeature(int input);
}

和:

class BasicFeatureFactory : IFeatureFactory
{
    public IFeature createFeature(int input)
    {
        switch (input)
        {
            case 1:
                return new FirstFeature();
            case 2:
                return new SecondFeature();
            default:
                return null;
        }
    }
}

将来,IFeature 会有依赖,所以我想以 IoC 的方式来做。

编辑:

消费者类 - IFeatureFactoryIUILayer 被注入 FeatureService 构造函数并使用 解析忍者

    private IFeatureFactory featureFactory;
    private IUILayer uiHandler;

    public FeatureService(IFeatureFactory featureFactory, IUILayer uiHandler)
    {
        this.featureFactory = featureFactory;
        this.uiHandler = uiHandler;
    }

    public void startService()
    {
        int userSelection = 0;
        uiHandler.displayMenu();
        userSelection = uiHandler.getSelection();
        while (userSelection != 5)
        {
            IFeature feature = featureFactory.createFeature(userSelection);
            if (feature != null)
            {
                IResult result = feature.execFeature();
                uiHandler.displayResult(result);
            }
            else
            {
                uiHandler.displayErrorMessage();
            }
            uiHandler.displayMenu();
            userSelection = uiHandler.getSelection();
        }
    }

和 IFeature 类:

public interface IFeature
{
    IResult execFeature();
}

绑定:

    public override void Load()
    {
        Bind<IFeatureFactory>().To<BasicFeatureFactory>();
        Bind<IUILayer>().To<ConsoleUILayer>();
    }

如何使用 Ninject.Extensions.Factory 将此 Factory Pattern 转换为 IoC?请记住,IFeature 的创建取决于用户输入。

【问题讨论】:

  • 您能否展示IFeatureFactoryIFeature 的消费者将如何使用这些抽象?
  • 那么,最好的解决方案是什么?使用 Ninject.Extensions.Factory 是个好主意吗?还是这样?我该如何使用 Ninject.Extensions.Factory 呢?谢谢。
  • 我认为这个问题对于您要解决的问题有点含糊。您是否要求 SO 帮助您的应用程序面向未来?如果是这样,如果有 10 个人回答它,你可能最终会得到 12 种不同的意见,这些意见基于每个人都认为是处理面向未来的“正确”方式。我能说的最好的事情是,首先很好地模拟你的需求,然后再考虑如何让你的工具支持它。

标签: c# ninject inversion-of-control ioc-container factory-pattern


【解决方案1】:

在我看来,您似乎有 2 个选项来重构您的代码以获得 ninject 的全部好处。
您现在的工作方式与 pure di 没有什么不同(这没有什么问题,在某些情况下更好),但正如您所说,您想充分使用 ninject 功能。

选项一
不是将 IFeatureFactory 注入 FeatureService 而是注入接口 IFeatureProvider,如下所示:

public interface IFeatureProvider
{ 
    IFeature GetFeature(int featureType);
}

现在您的 FeatureService 将从该提供商而不是工厂获取请求的功能。
您将需要实现 IFeatureProvider,为此您还需要 2 个接口 IFirstFeatureFactory 和 ISecondFeatureFactory:

public interface IFirstFeatureFactory
{
    IFeature CreateFirstFeature();
}

public interface ISecondFeatureFactory
{
    IFeature CreateSecondFeature();
}

现在是 IFeatureProvider impelementaion:

    public class FeatureProvider: IFeatureProvider
    {
        private readonly IFirstFeatureFactory _firstFeatureFactory;
        private readonly ISecondFeatureFactory _secondFeatureFactory;

        public FeatureProvider(IFirstFeatureFactory firstFeatureFactory, ISecondFeatureFactory secondFeatureFactory)
        {
            _firstFeatureFactory=firstFeatureFactory;
            _secondFeatureFactory=secondFeatureFactory;
        }

        public IFeautre GetFeature(int featureType)
        {
            switch(featureType)
           {
                 case 1:
                    return _firstFeatureFactory.CreateFirstFeature();
                 case 2:
                    return _secondFeatureFactory.CreateSecondFeature();
                 default:
                    return null;
           }
        }
    }

您应该注意到我只是将负责“新”的对象提取到另一个接口中。
我们不会实现这两个工厂接口,因为如果我们正确绑定它,ninject 会为我们做这件事。
绑定:

Bind<IFeature>().ToFeature<FirstFeature>().NamedLikeFactoryMethod((IFirstFeatureFactory o) => o.CreateFirstFeature());
Bind<IFeature>().ToFeature<SecondFeature>().NamedLikeFactoryMethod((ISecondFeatureFactory o) => o.CreateSecondFeature());
Bind<IFirstFeatureFactory>().ToFactory();
Bind<ISecondFeatureFactory>().ToFactory();
Bind<IFeatureProvider>().To<FeatureProivder>();

这个 'NameLikeFactoryMethod' 绑定等同于使用命名绑定,就像我使用 here 一样,现在它是 the recommended way by ninject for factories

这里要注意的重要一点是,您并没有自己实现 IFirstFeatureFactory 和 ISecondFeatureFactory,而是为此使用了 ninject 功能。

此选项的主要缺点是当我们需要添加更多功能时,我们需要创建除功能本身之外的另一个 FeatureFactory,并且还要更改 FeatureProvider 来处理它。 如果功能不经常更改,则此选项可能很好且简单,但如果更改,则可能会成为维护的噩梦,这就是我建议选项 2 的原因。

选项二
在此选项中,我们根本不会创建任何提供程序类,并将所有创建逻辑放在工厂中。
IFeatureFactory 接口看起来与您现在的接口非常相似,但我们将使用字符串而不是使用 int 作为参数(这将更适合命名绑定,我们很快就会看到)。

public interface IFeatureFactory
{
    IFeature CreateFeature(string featureName);
}

我们不会自己实现这个接口,让 ninject 为我们做,但是我们需要告诉 ninject 使用 CearteFeature 的第一个参数来检测要实例化哪个实现(FirstFeatue 或 SecondFeature)。
为此,我们将需要具有此行为的自定义实例提供程序作为 StandardInstanceProvider 使用其他约定来选择要实例化的实现(默认约定 in this article)。
幸运的是,ninject 通过UseFirstArgumentAsNameInstanceProvider 准确地展示了我们如何快速实现它。
现在绑定:

Bind<IFeature>().To<FirstFeature>().Named("FirstFeature");
Bind<IFeature>().To<FirstFeature>().Named("SecondFeature");
Bind<IFeatureFactory>().ToFactory(() => new UseFirstArgumentAsNameInstanceProvider());

这里要注意的事情:

  • 我们不会自己实现工厂接口。
  • 我们为每个实现使用命名绑定,我们将根据我们将传递给工厂方法的 featureName 从工厂获取实现(这就是为什么我更喜欢参数是字符串)。
    请注意,如果您传递的功能名称不是“FirstFeature”或“SecondFeature”,则会引发异常。
  • 如前所述,我们使用 UseFirstArgumentAsNameInstanceProvider 作为我们工厂的实例提供程序。

这已经解决了第一个选项中的问题,就好像我们想添加新功能一样,我们只需要创建它并将其绑定到他的界面并使用他的名字。
现在工厂的客户端可以使用这个新名称向工厂功能请求,而无需更改其他类。

结论
通过选择上面的一个选项而不是纯 di,我们将获得让 ninject 内核在工厂中创建我们的对象而不是我们自己做所有“新”的好处。
尽管不使用 IoC 容器并没有错,但如果 IFeature 实现内部存在很大的依赖关系图,因为 ninject 会为我们注入它们,这确实可以帮助我们。 通过执行其中一个选项,我们将完全使用 ninject 功能,而无需使用被视为反模式的“服务定位器”。

【讨论】:

  • 感谢您提供非常详细的答案。我最终按照您的建议使用了第二个选项,但我遇到的一个问题是 - 假设我有 FirstFeature 和 SecondFeature 的实现,基于输入“1”和“2”。比用户输入“3” - 我如何处理此选项返回 null ?因为现在应用程序崩溃说“没有匹配的绑定可用,并且类型不是自绑定的”。再次非常感谢
  • 我的意思是,处理此问题的唯一方法是在获取 IFeature 之前检查输入吗?或者也许 ninject 有办法处理这类问题。谢谢
  • 我几乎可以肯定,如果你覆盖另一个 ninject 默认行为,它是可能的。但我认为你不应该这样做,最好检查输入或将对工厂的调用包装在 try catch 块中。
【解决方案2】:

您可以将这些功能注入到BasicFeatureFactory 的构造函数中。

class BasicFeatureFactory : IFeatureFactory
{
    FirstFeature feature1;
    SecondFeature feature2;

    public BasicFeatureFactory(FirstFeature feature1, SecondFeature feature2) {
        this.feature1 = feature1;
        this.feature2 = feature2;
    }

    public IFeature createFeature(int input) {
        switch (input) {
            case 1: return this.feature1;
            case 2: return this.feature2;
            default: return null;
        }
    }
}

【讨论】:

  • 我唯一建议的是将 c'tor 参数更改为 IFeature 并使用 Ninject 的上下文绑定工具来确定“FirstFeature”和“SecondFeature”是什么:github.com/ninject/Ninject/wiki/Contextual-Binding
  • @JoeAmenta:我想说“最简单的事情可能会起作用”:)。您还可以注入内核本身并从容器中解析。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多