5

私は、DI/IoC、NHibernate に頭を悩ませ、開発中のアプリケーションでそれらをうまく連携させようとしています。私はNHibernateとDI/IoCの両方にまったく慣れていないので、私がやっていることは賢明な方法であるかどうかはよくわかりません。これはシナリオです:

このアプリケーションは、特定の金融取引の特定の値 (証拠金と呼ばれる) を計算する機能をユーザーに提供します。各トランザクションのマージン値の計算は、抽象的な MarginCalculator クラスの具体的な実装によって実行され、使用される具体的な実装は、特定のトランザクション (製品オブジェクトの特定のフィールドによって与えられる) の製品のタイプによって異なります。具体的な電卓クラスには、製品クラスのプロパティを介してアクセスします。すなわち

public class Transaction
{
    private double _margin;
    private Product _product;
    private Client _client;

    public double Margin { get; }
    public Product Product { get; }
    public Client Client { get; }

    public Transaction(Product p, Client c)
    {
        _product = p;
        _client = c;
    }

    public void CalculateMargin()
    {
        _margin = _product.MarginCalculator.CalculateMargin();
    }
}

public class Product
{
    private string _id;
    private string _productType;
    ... Other fields

    public string Id { get; }
    public string ProductType { get; }
    public MarginCalculator MarginCalculator
    {
        get { return MarginCalculatorAssembler.Instance.CreateMarginCalculatorFor(this.ProductType); }
    }
}

public class MarginCalculatorAssembler
{
    public static readonly MarginCalculatorAssembler Instance = new MarginCalculatorAssembler();

    private MarginCalculatorAssembler ()
    {
    }

    public MarginCalculator CreateMarginCalculatorFor(string productType)
    {
        switch (productType)
        {
            case "A":
                return new ConcreteMarginCalculatorA();
            case "B":
                return new ConcreteMarginCalculatorB();
            default:
                throw new ArgumentException();
        }
    }
}

public abstract class MarginCalculator
{
    public abstract double CalculateMargin();
}

public class ConcreteMarginCalculatorA : MarginCalculator
{
    public override double CalculateMargin
    {
        // Perform actual calculation
    }
}

public class ConcreteMarginCalculatorB : MarginCalculator
{
    public override double CalculateMargin
    {
        // Perform actual calculation
    }
}

ユーザーがドロップダウンから特定のクライアントと製品を選択すると、対応する clientId と productId がリポジトリに渡され、NHibernate を使用してトランザクション オブジェクトに注入される前に製品とクライアント オブジェクトにデータが入力されます。私の現在のセットアップでは、トランザクションはコンストラクター依存性注入を介して製品とクライアントの依存性を受け取ります (まだ IoC コンテナーは使用されていません)。

public class ProductRepository : IRepository<Product>
{
    public Product GetById(string id)
    {
        using (ISession session = NHibernateHelper.OpenSession())
            return session.Get<Product>(id);
    }
}

/* Similar repository for Clients */

IRepository<Client> clientRepository = new ClientRepository();
IRepository<Product> productRepository = new ProductRepository();
Client c = clientRepository.GetById(clientId);
Product p = productRepository.GetById(productId);

Transaction t = new Transaction(p, c);

以下は、私がアイデアを得ることを望んでいるものです。

A. Product ドメイン オブジェクトを介して MarginCalculator (本質的にはサービス) にアクセスすることは問題ないと考えられますか、それとも、ここで提案されているように ( http://stackoverflow.com/questions/340461/dependency-injection-with-nhibernate -objects ) ドメイン オブジェクトからサービスの依存関係を削除し、代わりに抽象的な MarginCalculator を依存関係として受け取る新しい TransactionProcessor クラスを作成するようにコードを再構築する必要があります (ここで説明されている行に沿って ( http://www.lostechies.com ) /blogs/jimmy_bogard/archive/2008/03/31/ptom-the-dependency-inversion-principle.aspx ) すなわち

public class TransactionProcessor
{
    private readonly MarginCalculator _marginCalculator;

    public TransactionProcessor(MarginCalculator marginCalculator)
    {
        _marginCalculator = marginCalculator;
    }

    public double CalculateMargin(Transaction t)
    {
        return _marginCalculator.CalculateMargin(Transaction t);
    }
}

public abstract class MarginCalculator
{
    public abstract double CalculateMargin(Transaction t);
}

B. IoC コンテナーを使用して、NHibernate に入力/生成された製品とクライアントの依存関係が注入されたトランザクション オブジェクトを取得することは可能ですか? つまり、両方ともユーザーが提供する productId と clientId を指定すると、次のようなものを持つことができます。

// pseudocode
Transaction t = IoC.Resolve<Transaction>(productId, clientId);

コンテナーがトランザクション オブジェクトの製品とクライアントの依存関係を解決するように、NHibernate を使用して、productId と clientId に基づいて製品とクライアントを入力し、入力された製品とクライアントをトランザクションに挿入しますか?

C.典型的な DI シナリオでは、クラス A がインターフェイス B に依存している場合、次のことが行われる可能性があります。

IInterfaceB b = new ClassB();
A a = new A(b);

interface IInterfaceB
{
}

class B : IInterfaceB
{
}

public class A
{
    private IIntefaceB _b;

    public A(IInterfaceB b)
    {
        _b = b;
    }
}

ただし、これは実質的に DI のすべての例が示されている方法であり、IInterfaceB (この場合はクラス B) の実装者が設計時にわかっていることを前提としています。実装者が実行時に決定されるような方法で DI を使用する方法はありますか?

どうもありがとう

マシュー

4

6 に答える 6

1

A)Productドメインオブジェクトを介してMarginCalculatorにアクセスする場合は、仲介者を切り取って、DI/IOCコンテナにMarginCalculatorを注入させることもできます。ほとんどのDI/IOCコンテナは、オブジェクト構築のボイラープレートコードのほとんどを実行するため、MarginCalculatorAssemblerを取り除くこともできます。

BとC)それは非常に可能です。実際、 LinFuを使用した場合のコードは次のようになります。

//Transactionクラスを変更する必要はありません
パブリッククラストランザクション
{{
    プライベートdouble_margin;
    プライベート製品_product;
    プライベートクライアント_client;

    public double Margin {get; }
    public Product Product {get; }
    public Client Client {get; }

    パブリックトランザクション(製品p、クライアントc)
    {{
        _product = p;
        _client = c;
    }

    public voidCalculateMargin()
    {{
        _margin = _product.MarginCalculator.CalculateMargin();
    }
}

DI / IOCを取得して、製品とクライアントのインスタンスをコンストラクターに挿入できれば便利ですが、その前に、依存関係をコンテナーに登録する必要があります。LinFu.IOCでそれを行う方法は次のとおりです。

//次に、LinFuに製品クラスを自動的に登録するように指示する必要があります。
[ファクトリ(typeof(Product))]
パブリッククラスProductFactory:IFactory
{{
     オブジェクトCreateInstance(IServiceRequestリクエスト)
     {{
          //コンテナからIRepositoryのコピーを取得します
          var repository = container.GetService>();

          // IDを取得します(これは、IDがInt32であることを前提としています)
          var id =(int)request.Arguments [0];

          //商品自体を返品します
          リポジトリを返します。GetById(id);
     }
}

//Clientクラスでも同じことを行います
//(注:物事を単純にするために単純なカットアンドペーストを行いました-重複を許してください)
[Factory(typeof(Client))]
パブリッククラスClientFactory:IFactory
{{
     オブジェクトCreateInstance(IServiceRequestリクエスト)
     {{
          //コンテナからIRepositoryのコピーを取得します
          var repository = container.GetService>();

          // IDを取得します(これは、IDがInt32であることを前提としています)
          var id =(int)request.Arguments [0];

          //クライアント自体を返します
          リポジトリを返します。GetById(id);
     }
}

[Factory(typeof(Transaction))]
パブリッククラスTransactionFactory:IFactory
{{
     オブジェクトCreateInstance(IServiceRequestリクエスト)
     {{
        //注:簡潔にするために、引数のチェックは削除されました
        var container = request.Container;
        var arguments = request.Arguments;
        var productId =(int)arguments [0];
        var clientId =(int)arguments [1];

        //製品とクライアントを取得します
        var product = container.GetService(productId);
        var client = container.GetService(clientId);

        //トランザクション自体を作成します
        新しいTransaction(product、client);を返します。
     }
}

//この実装をシングルトンにします
[Implements(typeof(MarginCalculator)、LifecycleType.Singleton)]
パブリッククラスConcreteMarginCalculatorA:MarginCalculator
{{
    public override doubleCalculateMargin()
    {{
        //実際の計算を実行します
    }
}

アセンブリの1つにすべてのコードをコンパイルしたら、それをコンテナにロードするために必要なことは次のとおりです。

var container = new ServiceContainer();
container.LoadFrom(AppDomain.CurrentDomain.BaseDIrectory、 "YourAssembly.dll");

...さて、楽しい部分です。指定された製品とクライアントIDを使用してトランザクションオブジェクトを作成するには、LinFu.IOCのコンテナに対して行う必要のある呼び出しを次に示します。

int productId = 12345;
int clientId = 54321;
文字列serviceName=null;

//擬似コードではありません:)
var transaction = container.GetService(serviceName、productId、clientId);

これが興味深いのは、依存関係がいくつもあるにもかかわらず、LinFuのIOCコンテナがボイラープレートコードの90%を処理するため、これらすべてを自分で行う必要がないことです。最良の部分は、上記のすべての実装が実行時にすべて決定/解決されることです。

プログラムの実行中に実装を実質的に交換でき、アプリケーションを再コンパイルしなくても実装を置き換えることもできます。あなたはここでより多くの情報を見つけることができます:

http://www.codeproject.com/KB/cs/LinFu_IOC.aspx

HTH :)

于 2008-12-23T01:15:26.843 に答える
1

これがあなたの質問に対する私の2番目の見解です:

A: ベスト プラクティスとしては、インターフェイス タイプに依存している限り、サービスの依存関係をドメイン オブジェクトに残すことができます。ほとんどの (すべてではないにしても) コンテナーは、そのタイプの注入を行うことができます。各サービスの依存関係をモックアウトするのは非常に簡単なので、具象クラスのすべての動作をテストできます。基本クラスを使用して一般的な CRUD 永続化作業を行うなど、特定のインターフェイス実装のボイラープレート実装をリファクタリングする場合にのみ、抽象クラスを使用することをお勧めします。

B と C:

この種の機能が利用可能であることを知っておくとよいでしょう。もっと重要な問題は、私がやろうとしていることが実際に一般的な慣行であるかどうか、そしてそれが良い慣行と見なされているかどうかということだと思います. すなわち

  1. >永続化フレームワーク(NHibernateなど)を使用して、コンテナに解決して事前に設定された依存関係を注入します。
  2. 具体的な実装が実行時に決定される抽象的な依存関係の具体的な実装をコンテナに注入させます。

また、IoC/DI/NHibernate の用語では、私が話していることには特定の名前がありますか? たとえば、この比較またはこの .net IoC フレームワークの比較にリストされている機能の 1 つですか? 他の IoC フレームワーク (Castle Windsor など) に LinFu のようなこれらの機能が含まれているかどうかについて読みたいのですが、説明しているものに特定の名前があるかどうかがわからないため、何を検索すればよいかわかりません:)

このリンクに投稿された比較を実際に参照していると思います。

1)AFAIK、サービスインジェクションを行うのは標準的な方法ですが、実行時にドメインオブジェクトIDを使用してこれらの依存関係を解決する必要があるため、言及しているタイプのインジェクションは他のフレームワークの一部では実行が困難です.また、すべてのコンテナーがそのタイプの動的解決 (別名「コンテキスト バインディング」) をサポートしているわけではありません。すべてが等しい場合 (およびこれが他のコンテナーで実行できると仮定すると)、DI/IoC に適用されると思われる唯一の「ベスト プラクティス」は、サービスの依存関係にインターフェイスを使用する必要があることです。

これらの依存関係を最終的にどのように構築して解決するかは、完全にあなた次第です。あなたの場合、コンテナ自体がほとんどの依存関係を排除できる限り、永続化フレームワークからこれらの依存関係を取得しても問題ありません。あなたのためのボイラープレート解決コード。

2) 具体的なサービス注入は DI/IOC フレームワークの標準であり、それらのほとんどは実行時に依存関係を解決できます。ただし、これらのフレームワークは、注入を行う方法と場所が異なります。

参考までに、注目すべき 2 つの機能は、コンストラクター インジェクションプロパティ インジェクションです。コード例に基づいて、コンストラクター注入を使用する傾向があると思いますので、それぞれのフレームワークがそのタイプの注入をどのように行うかを監視することをお勧めします。HTH :)

于 2008-12-25T00:48:21.563 に答える
0

この投稿を ご覧くださいhttp://fabiomaulo.blogspot.com/2008/11/entities-behavior-injection.html

于 2008-12-25T13:45:09.513 に答える
0

「ドメイン駆動設計」によると、あなたのサービスは「ドメインサービス」であり、ドメインの残りの部分がそれを直接呼び出すか、それに依存することは問題ありません。

Nhibernateを使用する場合は、DAOSを提供する非常に人気のあるDIフレームワークであるSpring.netを確認してください。このフレームワークには、すでにセッションが注入されています。また、宣言型トランザクション(属性を使用したメソッドのマーキング)を使用することもできます。プロジェクトのドキュメントはとても良いです。

大事なことを言い忘れましたが、誤解しないでください。あなたがこのテクノロジーを使用しているのは、(DIの必要性があるとは思わない)という理由だけで、何かを学ぶためにそれをしているのであれば、これはすばらしいことです。しかし、他のすべての場合は間違っています。

よろしく

于 2008-12-23T13:18:21.143 に答える
0

パブロ、

コメントありがとうございます。

プロジェクト内で DI を使用する予定の 1 つの領域についてもう少し詳しく説明すると (あなたが言うように、DI について学ぶためだけでなく、必要だと思うため)、それがDIを使用する正しい場所。

元の投稿で述べたように、アプリケーションは MarginCalculator Service を利用します。

public abstract class MarginCalculator
{
    public abstract double CalculateMargin();
}

注: サービスは、抽象クラスまたはインターフェイスの場合があります。

具体的な実装 (DI 用語のコンポーネント?) は次のようになります。

public class ConcreteMarginCalculatorA : MarginCalculator
{
    private IDependencyService1 _dependencyService1;
    private IDependencyService2 _dependencyService2;

    // Constructor dependency injection
    public ConcreteMarginCalculatorA(
        IDependencyService1 dependencyService1,
        IDependencyService2 dependencyService2)
    {
        this._dependencyService1 = dependencyService1;
        this._dependencyService2 = dependencyService2;
    }

    public override double CalculateMargin
    {
        // _dependencyService1 and _dependencyService2 
        // required here to perform calcuation.
    }
}

public class ConcreteMarginCalculatorB : MarginCalculator
{
    private IDependencyService3 _dependencyService3;
    private IDependencyService4 _dependencyService4;

    // Constructor dependency injection
    public ConcreteMarginCalculatorB(
        IDependencyService3 dependencyService3,
        IDependencyService4 dependencyService4)
    {
        this._dependencyService3 = dependencyService3;
        this._dependencyService4 = dependencyService4;
    }

    public override double CalculateMargin
    {
        // _dependencyService3 and _dependencyService4 
        // required here to perform calcuation.
    }
}

具体的な Margin Calculators とその構築は、依存性注入を使用する必要がある場所と、IoC コンテナーを使用して依存性注入を処理する方法の完璧な例ではありませんか?

私がやろうとしていることは、DI/IoC がthis onethis oneなどの記事で説明されている方法と非常に似ていると思います。

最後に、パラメーター値に基づいてコンポーネント/インプリメンター (ConcreteMarginCalculatorA、ConcreteMarginCalculatorB など) を動的に解決するために、おそらく内部/子コンテナーを含むファクトリ クラスを使用します。これを達成するために、パラメーター値に基づいて実装者を選択できるAutofac ( http://code.google.com/p/autofac/ ) に傾倒しています ( http://code.google.com/p/autofac /wiki/ComponentCreation - セクション「パラメーター値に基づく実装者の選択」):

public class MarginCalculatorFactory
{
    private readonly IContainer _factoryLevelContainer;

    public MarginCalculatorFactory(IContainer mainContainer)
    {
        _factoryLevelContainer = mainContainer.CreateChildContainer()
        _factoryLevelContainer.RegisterType<MarginCalculator, ConcreteMarginCalculatorA>("ConcMC1");
        _factoryLevelContainer.RegisterType<MarginCalculator, ConcreteMarginCalculatorB>("ConcMC2");
}

public MarginCalculator CreateCalculator(string productType)
{
    return _factoryLevelContainer.Resolve<MarginCalculator>(productType);
}

}

最後に私ができるように:

marginCalculatorFactory.CreateCalculator(productType);

クライアント コードで、完全に解決された電卓を取得します。次に、Calculator は、TransactionProcessor Service に注入された依存関係になる可能性があります。

public class TransactionProcessor
{
    private readonly MarginCalculator _marginCalculator;
    private readonly Transaction _transaction;

    public TransactionProcessor(MarginCalculator marginCalculator
        ,Transaction transaction)
    {
            _marginCalculator = marginCalculator;
            _transaction = transaction
    }

    public double CalculateMargin(Transaction t)
    {
            return _marginCalculator.CalculateMargin(transaction);
    }
}

私は IoC/DI ゲーム全体に慣れていないので間違っているかもしれませんが、これはまさにDi/IoC が使用される種類のシナリオであるように思えます。他の人はどう思いますか?

ありがとう

マシュー

于 2008-12-23T16:41:46.817 に答える
0

フィリップ、

ご回答有難うございます!

B と C :

この種の機能が利用可能であることを知っておくとよいでしょう。もっと重要な問題は、私がやろうとしていることが実際に一般的な慣行であるかどうか、そしてそれが良い慣行と見なされているかどうかということだと思います. すなわち

  1. 永続化フレームワーク (NHibernate など) を使用して事前設定された依存関係をコンテナーに解決および注入させ、
  2. 具体的な実装が実行時に決定される抽象的な依存関係の具体的な実装をコンテナに注入させます。

また、IoC/DI/NHibernate の用語では、私が話していることには特定の名前がありますか? たとえば、この比較またはこの.net IoC フレームワークの比較にリストされている機能の 1 つ他の IoC フレームワーク (Castle Windsor など) に LinFu のようなこれらの機能が含まれているかどうかについて読みたいのですが、説明しているものに特定の名前があるかどうかがわからないため、何を検索すればよいかわかりません:)

A:

ベスト プラクティス (つまり、疎結合、テストなど) に関しては、ドメイン オブジェクトからサービスの依存関係を削除するか、そのままにしておく方がよいでしょうか?

ありがとう

マシュー

于 2008-12-23T07:51:33.660 に答える