3

私は、データベースへの接続、データベースからの特定の製品の選択(提供された基準に基づく)、およびこの製品での処理を処理する単純なコンソールアプリケーションを作成しています。このクラスのインスタンスにコマンドライン引数を格納しています。

public class Arguments
{
   public string ConnectionString { get; set; }
   public int ProductId { get; set; }
   public string ProductName { get; set; }
}

ある時点で、データベースから製品をフェッチする必要があります。そのために次のリポジトリを使用しています。

public interface IProductRepository
{
   Product GetById(int productId, string connectionString);
   Product GetByName(string productName, string connectionString);
}

次に、リポジトリの実装を、それを使用するクラスに注入します。例:

public class ProductProcessor
{
   private readonly IProductRepository productRepository;

   public ProductProcessor(IProductRepository productRepository)
   {
      this.productRepository = productRepository;
   }

   public void Process(Arguments arguments)
   {
      Product productToProcess;

      if (!string.IsNullOrEmpty(arguments.ProductName))
      {
         productToProcess = productRepository.GetByName(arguments.ProductName, arguments.ConnectionString);
      }
      else
      {
         productToProcess = productRepository.GetById(arguments.ProductId, arguments.ConnectionString);
      }

      // ....
   }
}

これは機能していますが、デザインについて私が気に入らないのは、のすべてのメソッドに引数IProductRepositoryがあることです。connectionString依存性注入が含まれていなかった場合、おそらく次のように書き直します。

public void Process(Arguments arguments)
{
   Product productToProcess;

   ProductRepository productRepository = new ProductRepository(arguments.ConnectionString);

   if (!string.IsNullOrEmpty(arguments.ProductName))
   {
      productToProcess = productRepository.GetByName(arguments.ProductName);
   }
   else
   {
      productToProcess = productRepository.GetById(arguments.ProductId);
   }

   // ....
}

これにより、よりシンプルで使いやすいインターフェイスを使用できるようになります。もちろん、現在ProductRepositoryはパラメーターなしのコンストラクターがなく、DIコンテナーで使用するのは困難です。理想的には、両方の長所を生かしたいと思います。つまり、ProductRepositoryコンストラクターからの接続文字列で初期化し、そのメソッドから接続文字列を削除します。これを達成するための最良のアプローチは何ですか?

私がすでに検討したいくつかのアプローチ:

  • 基本的にコンストラクターとして機能するメソッドInitialize(string connectionString)をに追加します。IProductRepository明らかな欠点は、メソッドで何かを行う前に、が呼び出されたかどうかを確認する必要があることですInitializeGetByIdGetByName
  • コンストラクタインジェクションを使用せず、代わりにServiceLocatorパターンを使用してインスタンス化しますProductRepository。私はServiceLocatorがあまり好きではありませんが、これはおそらく唯一の可能な解決策です。

より良い代替手段はありますか?

編集:回答から、もう少しコンテキストを投稿する必要があったことがわかります。DIコンテナとしてNinjectを使用しています。Program.csMainのメソッドで、すべての依存関係をコンテナーに登録し、アプリケーションへのエントリポイントとして機能するクラスをインスタンス化します。

public static void Main(string[] args)
{
    StandardKernel kernel = new StandardKernel();
    kernel.Bind<IArgumentsParser>().To<IArgumentsParser>();
    kernel.Bind<IProductProcessor>().To<ProductProcessor>();
    kernel.Bind<IProductRepository>().To<ProductRepository>();

    MainClass mainClass = kernel.Get<MainClass>();
    mainClass.Start(args);
}

MainClass外観は次のようになります。

public class MainClass
{
    private readonly IArgumentsParser argumentsParser;
    private readonly IProductProcessor productProcessor;        

    public MainClass(IArgumentsParser parser, IProductProcessor processor)
    {
        argumentsParser = parser;
        productProcessor = processor;
    }

    public void Start(string[] args)
    {
        Arguments parsedArguments = argumentsParser.Parse(args);
        productProcessor.Process(parsedArguments );
    }
}

これにより、Ninjectに依存し、グラフ全体を1か所(Mainメソッド)でのみ作成でき、アプリケーションの残りの部分はDIとコンテナーについて何も知りません。

できればそのままにしておきたいです。

4

5 に答える 5

4

現在のインターフェース設計は漏れやすい抽象化であることに同意するので、代わりに次のように定義しましょう。

public interface IProductRepository
{
    Product GetById(int productId);
    Product GetByName(string productName);
}

次に必要なのは、IProductRepository のインスタンスを作成できる抽象ファクトリです。

したがって、ProductProcessor は次のようになります。

public class ProductProcessor
{
    private readonly IProductRepositoryFactory productRepositoryFactory;

    public ProductProcessor(IProductRepositoryFactory productRepositoryFactory)
    {
        this.productRepositoryFactory = productRepositoryFactory;
    }

    public void Process(Arguments arguments)
    {
        Product productToProcess;

        var productRepository =
            this.productRepositoryFactory.Create(arguments.ConnectionString);
        if (!string.IsNullOrEmpty(arguments.ProductName))
        {
            productToProcess = productRepository.GetByName(arguments.ProductName);
        }
        else
        {
            productToProcess = productRepository.GetById(arguments.ProductId);
        }

        // ....
    }
}
于 2013-03-13T07:26:36.550 に答える
3

コマンドライン引数をモデル化する必要がある理由がまったくわかりません。各タイプへの依存関係を最小限に抑える必要があります。これは、製品リポジトリがコンストラクタ パラメータとして接続文字列を取得する必要があり (これは必須の依存関係であるため)、製品プロセッサが製品 ID と製品名を取得する必要があることを意味します (これが動的クエリを実行する最善の方法である場合)。

したがって、製品リポジトリがシングルトンであると仮定すると、登録 (接続文字列を渡す) を行う時点でそれを新しくし、これを抽象化に対して IC コンテナーに登録します。

次に、製品プロセッサを新しく作成し (製品 ID と製品名を渡す)、これを抽象化に対してシングルトンとして登録できます。その後、コンストラクター インジェクションを使用して、製品プロセッサを必要とする任意の型に渡すことができます。

于 2012-09-03T11:08:35.770 に答える
2

もちろん、現在の ProductRepository にはパラメーターなしのコンストラクターがなく、DI コンテナーで使用することは困難です。

逆に、ほとんどの DI コンテナーでは、パラメーター化されたコンストラクターを操作できます。実際、依存性注入を行う場合、コンストラクター注入が推奨されるアプローチです。これは、デフォルト以外のコンストラクターが存在することを意味します。プリミティブ型 (文字列の依存関係など) を受け取るコンストラクターを持つことは、一部のコンテナーが自動配線を実行できないことを意味する場合があります。自動配線とは、コンテナーが何を注入するかを判断することを意味します。ただし、製品リポジトリでは、初期化されたインスタンスをコンテナーに提供する (単一のインスタンスが必要な場合) か、ファクトリー デリゲートを提供する (呼び出しごとに新しいインスタンスが必要な場合) ことで、この問題を簡単に解決できます。使用するフレームワークによって異なりますが、次のようになります。

container.RegisterSingle(new SqlProductFactory("constring"));

のコンストラクターで接続文字列を指定する場合、(メソッド インジェクションを使用して) ファクトリに渡す必要はなく、クラスSqlProductFactoryでこの接続文字列は必要ありません。Arguments

于 2012-09-03T11:22:28.817 に答える
2

できることは、オブジェクトの作成をオブジェクトのルックアップから切り離すことです。DI コンテナーは、起動時に登録したインスタンスを検索します。その時点で、接続文字列をコンストラクター引数としてリポジトリに渡すことができます。

製品コードは次のようになります。

public class ProductRepository : IProductRepority
{
    private readonly string connString;
    public ProductRepository(string conn)
    {
        connString = conn;
    }
}

必要に応じて、接続文字列を別の型でラップすることもできます。重要な点は、DI は、起動時にタイプ グラフで行われたバインディングに基づいて、必要なインスタンスを注入することです。登録に基づいて、単に引数から接続文字列を抽出し、それを ProductRepository 登録に渡すことができます。

于 2012-09-03T11:26:38.153 に答える
0

編集

あなたが述べた問題を解決する方法については、次の回答を参照してください。

ただし、Windsor や nHibernate などの既存の IOC パッケージを使用することを強くお勧めします。詳細については、 https://stackoverflow.com/questions/2515124/whats-the-simplest-ioc-container-for-cを参照してください。

編集終了

ConnectionString をプロパティとして IProductRepository に追加してみませんか。

したがって、インターフェースは次のとおりです。

public interface IProductRepository
{
  string ConnectionString { get; set; }
  Product GetById(int productId);
  Product GetByName(string productName);
}

プロセッサは次のようになります。

 public void Process(Arguments arguments)
 {
 Product productToProcess;

 var productRepository = new ProductRepository 
      { ConnectionString = arguments.ConnectionString};

 if (!string.IsNullOrEmpty(arguments.ProductName))
    productToProcess = productRepository.GetByName(arguments.ProductName);
 else
    productToProcess = productRepository.GetById(arguments.ProductId);

 // ....
}
于 2012-09-03T11:08:26.127 に答える