33

警告、長い投稿が先です。

私は最近これについてよく考えていて、ここで満足のいく解決策を見つけるのに苦労しています. 例では C# と autofac を使用します。

問題

IoC は、ステートレス サービスの大きなツリーを構築するのに最適です。サービスを解決し、メソッド呼び出しにのみデータを渡します。偉大な。

場合によっては、データ パラメーターをサービスのコンストラクターに渡したいことがあります。それが工場の目的です。サービスを解決する代わりに、そのファクトリを解決し、パラメーターを指定して create メソッドを呼び出してサービスを取得します。もう少し作業がありますが、OKです。

時々、サービスを特定のスコープ内の同じインスタンスに解決したいと考えています。AutofacInstancePerLifeTimeScope()は非常に便利な機能を提供します。これにより、実行サブツリー内の同じインスタンスに常に解決できます。良い。

また、両方のアプローチを組み合わせたい場合もあります。コンストラクターにデータパラメーターが必要で、インスタンスのスコープが設定されています。これを達成するための満足のいく方法は見つかりませんでした。

ソリューション

1.メソッドの初期化

データをコンストラクターに渡す代わりに、Initializeメソッドに渡すだけです。

インターフェース:

interface IMyService
{
    void Initialize(Data data);
    void DoStuff();
}

クラス:

class MyService : IMyService
{
    private Data mData;
    public void Initialize(Data data)
    {
        mData = data;
    }

    public void DoStuff()
    {
        //...
    }
}

登録:

builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();

使用法:

var myService = context.Resolve<IMyService>();
myService.Init(data);

// somewhere else
var myService = context.Resolve<IMyService>();

初めてサービスを解決して Initialize を呼び出した後、同じコンテキスト内で問題なく解決し、初期化された同じインスタンスを取得できます。Initialize呼び出す前に使用できないオブジェクトがあるという事実は好きではありません。Initialize() を呼び出す前に、インスタンスが解決されて別の場所で使用される危険性があります。

2.ホルダーパターン

これは、データ オブジェクトへの参照を保持するパターンであり、データ オブジェクト自体を注入する代わりに、ホルダー オブジェクトを注入します。

インターフェース:

interface IMyService
{
    void DoStuff();
}

クラス:

class MyService : IMyService
{
    private Data mData;
    public MyService(IDataHolder dataHolder)
    {
        mData = dataHolder.Data;
    }

    public void DoStuff()
    {
        //...
    }
}

登録:

builder.RegisterType<MyService>().As<IMyService>();
builder.RegisterType<DataHolder>().As<IDataHolder>().InstancePerLifetimeScope();

使用法:

var holder = context.Resolve<IDataHolder>();
holder.Data = data;

// somewhere else
var myService = context.Resolve<IMyService>();

インスタンスを保持する責任を別のクラスに移したので、これは少し良くなりました。他のサービスでもホルダーを使用できるようになりました。その他の利点は、必要に応じてホルダー内のデータをホットスワップできることです。コードが難読化され、テスト中にモックしなければならない別のインターフェイスが追加されるという事実は好きではありません。

3.コンテナにインスタンスを保持させる

インターフェース:

interface IMyService
{
    void DoStuff();
}

クラス:

class MyService : IMyService
{
    private Data mData;
    public MyService(Data data)
    {
        mData = dataHolder.Data;
    }

    public void DoStuff()
    {
        //...
    }
}

登録:

builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();

使用法:

var myServiceFactory = context.Resolve<Func<Data, IMyService>>();
myServiceFactory(data);

// somewhere else
var myService = context.Resolve<IMyService>();

それは正しい。autofac が保存してくれるので、ファクトリ コールの結果はどこにも保存しません。これは、コードを読む人にとってはかなり驚くべきことです。autofac がこのように使用されることを意図していたのかどうかはわかりません。これの良いところは、インスタンスを保持するための追加の初期化メソッドも追加のクラスも必要ないことです。

質問

これについてどう思いますか?実行時のデータ パラメーターと有効期間のスコープを使用して状況をどのように処理しますか? より良いアプローチがありませんか?

4

5 に答える 5

6

Autofac は、ライフタイム スコープの拡張により、すぐに使用できるようになりました。このBeginLifetimeScope()メソッドには、Action<ContainerBuilder>その有効期間スコープのみに固有の新しい登録を追加できる を受け取るオーバーロードがあります。したがって、与えられた例では、次のようになります。

var builder = new ContainerBuilder();
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
var container = builder.Build();

using(var scope = container.BeginLifetimeScope(
  builder =>
  {
    builder.RegisterInstance(new Data(....));
  }))
{
  // References to 'IMyService' will always be resolved to the same instance within this lifetime scop
  // References to 'Data' will be resolved to the instance registered just for this lifetime scope.
  var svc = scope.Resolve<IMyService>();
}
于 2016-11-22T08:50:44.837 に答える
3

ほとんどの場合、ランタイム データは、x数学関数のように、任意のプロセスで渡す必要がある非静的な情報であるため、これを処理する最も簡単な方法は、関数でパラメーターを使用することです。

class MyService : IMyService
{
    public MyService(){}

    public void DoStuff(Data mData)
    {
        //...
    }
}

var myService = context.Resolve<IMyService>();
myService.DoStuff(data);

ただし、例が単なる例であり、クラスがより多くのプロセスを実行するためにランタイムデータを保持する必要があり、すべての関数に同じ引数を渡したくないため、質問していると仮定します。

1.- すべての Resolve でランタイム データのスコープを失わない場合は、次の方法で解決できますTypedParameter

例:

//initilization
var builder = new ContainerBuilder();
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
var container = builder.Build();

//any point of your app
Data mData = new Data("runtimeData"); // must to be accesible in every place you Resolve

using(var scope = container.BeginLifetimeScope())
{

  var service = scope.Resolve<IMyService>(new TypedParameter(typeof(Data), mData));
service.DoStuff();
}

using(var scope = container.BeginLifetimeScope())
{

  var service2 = scope.Resolve<IMyService>(new TypedParameter(typeof(Data), mData));
service2.DoStuff();
}

2.-解決しているすべての場所にランタイムデータへの参照がない場合は、ランタイムデータをいつどこで作成するかをRegisterInstanceできます。Autofac はDirect Depency PolicymDataのおかげでインスタンスに感染する必要があります

//initilization
var builder = new ContainerBuilder();
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();
var container = builder.Build();

//where you create or modify runtime data. When runtime data changes you have to update the container again.
var mData = new Data("runtimeData");
updatedBuilder= new ContainerBuilder();
updatedBuilder.RegisterInstance(mData).As<Data>
updatedBuilder.Update(builder);

//in any point of your app
using(var scope = updatedBuilder.BeginLifetimeScope())
    {

      var service = scope.Resolve<IMyService>();
    service.DoStuff();
    }

//in any other point of your app
    using(var scope = updatedBuilder.BeginLifetimeScope())
    {

      var service2 = scope.Resolve<IMyService>();
    service2.DoStuff();
    }
于 2015-05-21T09:13:32.003 に答える
1

私が正しく理解している場合は、コンストラクターにいくつかのパラメーターを渡しながら、オブジェクトの作成をコンテナーに委譲することにより、ファクトリを使用したいと考えています。

これは、型付きのファクトリ機能を持つキャッスル ウィンザーに実装されています。

解決したいクラスの例:

public interface IMyService
{
    void Do();
}

public class MyService : IMyService
{
    private readonly Data _data;
    private readonly IDependency _dependency;

    public MyService(Data data, IDependency dependency)
    {
        _data = data;
        _dependency = dependency;
    }

    public void Do()
    {
        throw new System.NotImplementedException();
    }
}

public class Data
{    
}

public interface IDependency
{         
}

public class Dependency : IDependency
{
}

ファクトリ インターフェイスを作成します。

public interface IMyServiceFactory
{
    IMyService Create(Data data);
    void Release(IMyService service);
}

Castle Windsor は Dynamic Proxy を使用して実装を生成するため、このインターフェイスは実装しません。ここに重要な詳細があります: ファクトリ メソッドのパラメーター名 (データ) とコンストラクターのパラメーター名 (データ) は一致する必要があります。

次に、登録を行い、値を解決しようとします。

[Test]
public void ResolveByFactory()
{
    WindsorContainer container = new WindsorContainer();
    container.AddFacility<TypedFactoryFacility>();
    container.Register(Component.For<IMyServiceFactory>().AsFactory());
    container.Register(Component.For<IMyService>().ImplementedBy<MyService>().LifestyleScoped());
    container.Register(Component.For<IDependency>().ImplementedBy<Dependency>().LifestyleScoped());

    IMyServiceFactory factory = container.Resolve<IMyServiceFactory>();

    IMyService myService1;
    IMyService myService2;

    using (container.BeginScope())
    {
        myService1 = factory.Create(new Data());
        myService2 = factory.Create(new Data());

        myService1.Should().BeSameAs(myService2);
    }

    using (container.BeginScope())
    {
        IMyService myService3 = factory.Create(new Data());

        myService3.Should().NotBeSameAs(myService1);
        myService3.Should().NotBeSameAs(myService2);
    }
} 

同じスコープで作成されたオブジェクトは同じ参照であることがわかります。これがあなたが望む動作であるかどうか教えてください。

于 2015-05-21T22:38:36.550 に答える