web-dev-qa-db-ja.com

IoC(Ninject)とファクトリー

次のコードがある場合:

public class RobotNavigationService : IRobotNavigationService {
  public RobotNavigationService(IRobotFactory robotFactory) {
    //...
  }
}
public class RobotFactory : IRobotFactory {
  public IRobot Create(string nameOfRobot) {
    if (name == "Maximilian") {
      return new KillerRobot(); 
    } else {
      return new StandardRobot();
    }
  }
}

私の質問は、ここで制御の反転を行う適切な方法は何ですか? KillerRobotおよびStandardRobotコンクリートをFactoryクラスに追加したくないのですか?そして、私はそれらをIoC.Get <>経由で持ち込みたくありませんか? bcそれはサービスロケーションであり、真のIoCではありませんか? 実行時にコンクリートを切り替えるの問題に取り組むためのより良い方法はありますか?

22
BuddyJoe

あなたのサンプルでは、​​あなたは完全に素晴らしい工場実装を持っており、私は何も変更しません。

ただし、KillerRobotクラスとStandardRobotクラスには実際には独自の依存関係があると思われます。 IoCコンテナをRobotFactoryに公開したくないことに同意します。

1つのオプションは、ninjectファクトリ拡張を使用することです。

https://github.com/ninject/ninject.extensions.factory/wiki

これは、ファクトリを注入する2つの方法を提供します。インターフェイスによる方法と、IRobot(またはその他)を返すFuncを注入する方法です。

インターフェイスベースのファクトリ作成のサンプル: https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface

Funcベースのサンプル: https://github.com/ninject/ninject.extensions.factory/wiki/Func

必要に応じて、IoC初期化コードでfuncをバインドすることによっても実行できます。何かのようなもの:

var factoryMethod = new Func<string, IRobot>(nameOfRobot =>
                        {
                            if (nameOfRobot == "Maximilian")
                            {
                                return _ninjectKernel.Get<KillerRobot>();
                            }
                            else
                            {
                                return _ninjectKernel.Get<StandardRobot>();
                            }

                        });
_ninjectKernel.Bind<Func<string, IRobot>>().ToConstant(factoryMethod);

ナビゲーションサービスは次のようになります。

    public class RobotNavigationService
    {
        public RobotNavigationService(Func<string, IRobot> robotFactory)
        {
            var killer = robotFactory("Maximilian");
            var standard = robotFactory("");
        }
    }

もちろん、このアプローチの問題は、IoC初期化の内部でファクトリメソッドを記述していることです。おそらく最良のトレードオフではありません...

ファクトリエクステンションは、いくつかの規則ベースのアプローチを提供することでこれを解決しようとします。したがって、状況依存の依存関係を追加して、通常のDIチェーンを維持できます。

34
Shaun Rowan

あなたがすべき方法:

_kernel.Bind<IRobot>().To<KillingRobot>("maximilliam");
kernel.Bind<IRobot>().To<StandardRobot>("standard");
kernel.Bind<IRobotFactory>().ToFactory();

public interface IRobotFactory
{
    IRobot Create(string name);
}
_

しかし、この方法ではnull名が失われると思うので、_IRobotFactory.Create_を呼び出すときは、パラメーターを介して正しい名前が送信されていることを確認する必要があります。

インターフェイスバインディングでToFactory()を使用する場合、IResolutionRootを受信して​​Get()を呼び出すCastle(または動的プロキシ)を使用してプロキシを作成するだけです。

KillerRobotおよびStandardRobotコンクリートをFactoryクラスに追加したくないですか?

おそらくそうすることをお勧めします。具体的なオブジェクトをインスタンス化しない場合、ファクトリの目的は何でしょうか?私はあなたがどこから来ているのかわかると思います-IRobotが契約を記述している場合、インジェクションコンテナはそれを作成する責任があるべきではありませんか?それはコンテナの目的ではありませんか?

おそらく。ただし、newingオブジェクトを担当する具体的なファクトリを返すことは、IoCの世界ではかなり標準的なパターンのようです。コンクリート工場に実際の仕事をさせるのは原則に反するとは思いません。

3
Tom W

いくつかの作業を行うためにC#クラスを返す大規模なswitchステートメントをクリーンアップする方法を探していました(ここではコードの臭い)。

Ninjectモジュール(基本的には長いswitchケースの模倣ですが、diffファイル)で各インターフェイスを具体的な実装に明示的にマッピングしたくなかったので、すべてのインターフェイスを自動的にバインドするようにモジュールを設定しました。

public class FactoryModule: NinjectModule
{
    public override void Load()
    {
        Kernel.Bind(x => x.FromThisAssembly()
                            .IncludingNonPublicTypes()
                            .SelectAllClasses()
                            .InNamespaceOf<FactoryModule>()
                            .BindAllInterfaces()
                            .Configure(b => b.InSingletonScope()));
    }
}

次に、ファクトリクラスを作成し、IKernalを使用してシングルトンインスタンスを介して指定されたインターフェイスとその実装を取得するStandardKernalを実装します。

    public class CarFactoryKernel : StandardKernel, ICarFactoryKernel{
    public static readonly ICarFactoryKernel _instance = new CarFactoryKernel();

    public static ICarFactoryKernel Instance { get => _instance; }

    private CarFactoryKernel()
    {
        var carFactoryModeule = new List<INinjectModule> { new FactoryModule() };

        Load(carFactoryModeule);
    }

    public ICar GetCarFromFactory(string name)
    {
        var cars = this.GetAll<ICar>();
        foreach (var car in cars)
        {
            if (car.CarModel == name)
            {
                return car;
            }
        }

        return null;
    }
}

public interface ICarFactoryKernel : IKernel
{
    ICar GetCarFromFactory(string name);
}

次に、StandardKernel実装は、クラスを装飾するインターフェイスで選択した識別子によって、任意のインターフェイスで取得できます。

例えば。:

    public interface ICar
{
    string CarModel { get; }
    string Drive { get; }
    string Reverse { get; }
}

public class Lamborghini : ICar
{
    private string _carmodel;
    public string CarModel { get => _carmodel; }
    public string Drive => "Drive the Lamborghini forward!";
    public string Reverse => "Drive the Lamborghini backward!";

    public Lamborghini()
    {
        _carmodel = "Lamborghini";
    }
}

使用法:

        [Test]
    public void TestDependencyInjection()
    {
        var ferrari = CarFactoryKernel.Instance.GetCarFromFactory("Ferrari");
        Assert.That(ferrari, Is.Not.Null);
        Assert.That(ferrari, Is.Not.Null.And.InstanceOf(typeof(Ferrari)));

        Assert.AreEqual("Drive the Ferrari forward!", ferrari.Drive);
        Assert.AreEqual("Drive the Ferrari backward!", ferrari.Reverse);

        var lambo = CarFactoryKernel.Instance.GetCarFromFactory("Lamborghini");
        Assert.That(lambo, Is.Not.Null);
        Assert.That(lambo, Is.Not.Null.And.InstanceOf(typeof(Lamborghini)));

        Assert.AreEqual("Drive the Lamborghini forward!", lambo.Drive);
        Assert.AreEqual("Drive the Lamborghini backward!", lambo.Reverse);
    }
0
Brian Wells