C# - 使用依赖关系注入(ninject)而不是工厂模式



我已经读了很多关于这个主题的文章,但无法完全掌握它。

我正在尝试使用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 构造函数中,并使用 Ninject 进行解析。

    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 将此工厂模式转换为 IoC? 请记住,IFeature 的创建取决于用户输入。

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

备选方案一
不要将 IFeatureFactory 注入 FeatureService,而是注入接口 IFeatureProvider,如下所示:

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

现在,您的要素服务将从此提供程序(而不是工厂)获取请求的功能。
您将需要实现 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"绑定等效于使用命名绑定,就像我在这里所做的那样,现在是工厂ninject的推荐方式。

这里需要注意的重要一点是,你不是自己实现IFirstFeatureFactory和ISecondFeatureFactory,而是为此使用了ninject函数。

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

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

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

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

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

这里要注意的事项:

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

这解决了第一个选项中的问题,因为如果我们想添加新功能,我们只需要创建它并将其绑定到带有他的名字的界面。
现在,工厂的客户端可以使用此新名称从工厂功能中请求,而无需更改其他类。

解释
通过选择上面的选项之一而不是纯 di,我们将获得让 nin注入内核在工厂中创建我们的对象的好处,
而不是自己做所有"新"。虽然不使用 IoC 容器并没有错,但在 IFeature 实现中存在大量依赖关系图的情况下,这确实可以帮助我们,因为 ninject 会为我们注入它们。通过执行此选项之一,我们完全使用了 nin注入功能,而无需使用被视为反模式的"服务定位器"。

你可以把这些特性注入到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;
        }
    }
}

最新更新