0

私はMVCプロジェクトの新しいフェーズに取り組んでいます。最初のフェーズはかなり小さく、それほど複雑ではありませんでした。次のフェーズは非常に複雑で、はるかに大きくなります。最初のフェーズでは、さまざまな理由で徹底的な自動テストを行うというアイデアについて話し合い、破棄しました。

新しいフェーズでは、私たちのチームに自動テストに投資してもらいたいと思います。テストを制御可能にするためにデータベースの作業をスタブ化する必要があるため、依存性注入なしで役立つことは何もできないと思います。

私が苦労しているのは、依存性注入をビジネスレイヤーに後付けすることです。既存のビジネスオブジェクトには状態と動作があり、(静的メソッドを介して)自身のインスタンスをロードする責任があります。たとえば、次のように単純化しすぎた例があります。

public class User
{
    public User(string userName)
    {}

    public bool Authenticate()
    {}

    public static User GetByUserName(string userName)
    {
      //Do some DB querying, and then map the data object to a new instance:
      UserRepository repository = new UserRepository();
      var databaseObject = repository.list(u => u.userName = userName);
      User user = new User();
      user.userName = databaseObject.userName;
      // populate other fields
      return user;
    }
 }

一見すると、次のことが必要になるようです。

  1. コンストラクターの依存関係としてリポジトリーを挿入します
  2. GetByUserNameを、Userの静的メソッドではなくなるように変更します
  3. GetByUserNameメソッドでUserのインスタンスを作成できるように、ある種のファクトリを実行します

これらすべてを合理的な方法で行う方法がわかりません。#1は些細なことですが、#2と#3はそれほど些細なことではありません。これが私が#2を行う方法について考えていることです:

  1. 構造はほとんどそのままにして、staticキーワードを削除するだけです。つまり、ユーザー名でユーザーを取得するには、Userオブジェクトのインスタンスを作成してから、GetByUserNameメソッドを呼び出す必要があります。これは私の非常に洗練された匂いテストに合格しません。
  2. ユーザーのロードと保存の責任をある種のUserCollectionオブジェクトに移動します。これは、ほとんどすべてのビジネスオブジェクトが基礎となるコレクションオブジェクトを必要とし、ドメインモデルをより貧血にするため、私には疑わしいようです。
  3. リポジトリと直接インターフェースします。現在、コントローラーはUser.GetByUserName()を呼び出して、ユーザードメインオブジェクトを取得して作業を行う場合があります。代わりに、コントローラーはリポジトリーを認識し、Repository.list()を呼び出します。それは私には漏れているように思えます-突然、コントローラーはビジネス的なことをしているように感じます。

次に、#3があります。私のオプションは、IOCコンテナを使用してファクトリメソッド(autofacファクトリデリゲートなど)を挿入するか、各オブジェクトのファクトリを手動で作成することのようです。単体テストでautofacを使用するか、各オブジェクトのファクトリデリゲートをスタブ化する必要があるため、autofac方法論を使用するのはリークのようです。ある時点で、IOCコンテナを意識せずにDIを使用できるようにするためだけに、開発者にかなりのワークロードを課しているため、すべてのオブジェクトのファクトリを作成するのは厄介なようです。

私はこれらの主題を検索するためにできることをしましたが、上記の欠点を伴わない具体的な例は実際には多くありません。

「良いデザイン」を失うことなく、または「あまりにも多くの仕事」をすることなく、#2と#3を解決するためのいくつかのテクニックは何ですか?

4

2 に答える 2

1

クラスはUserリポジトリIMHOについて知らないはずです。リポジトリに接続すると、クラスの単体テストが困難になりUser、リポジトリを別のアセンブリに配置することにした場合は循環参照が作成されます。

典型的な設計はIUserRepository、CRUDメソッド(例)と実際のデータソースを指す GetUserByUserName1つ以上のクラスを定義するインターフェースを持つことです。UserRepository

(コントローラーなど)で動作する必要があるものがある場合は、UserRepositoryそれをとして注入しますIUserRepository。このようにして、モック/スタブリポジトリを使用してコントローラーを単体テストできます。

リポジトリはビジネスクラスを認識し、他のリポジトリに(大まかに)結合される場合があります(たとえば、リポジトリにユーザーをロールに入れるためにRoleが注入される場合があります)。IUserRepository

すべてのクラスに対して1つの「マスター」リポジトリから始めたいが、後でリポジトリを分割しなければならないリスクがある場合(これは大したことではないかもしれません)。

だから私はオプション#2とサブオプション#2をお勧めします(それをUserRepository対と呼びますUserCollection)。

于 2013-02-13T17:43:05.693 に答える
0

これをどのように実装するかは、ほとんどの場合、からデータベースオブジェクトを取得し、そこからインスタンスを設定するロジックを配置する場所に依存しUserRepositoryますUser。自分のデータを取得できる「スマート」モデルが好きな人もいれば、そうでない人もいます。

Userやや賢いクラスが欲しいようですね。クラスUserRepositoryのコンストラクターにを挿入し、別のインスタンスを作成する代わりに、リポジトリーを使用してこのインスタンスにデータを設定することで、これを実現できると思います。User UserUser

以下は実装例です。私の期待は、IoCコンテナーを使用していて、それにIUserRepositoryインターフェースを登録することです。単体テストなどのためにモックアウトしたい場合があるので、ユーザーリポジトリのインターフェイスが必要になると思います。

public class User
{
    public User(IUserRepository repository, string userName)
    {
      var databaseObject = repository.list(u => u.userName = userName);

      this.userName = databaseObject.userName;
      // populate other fields
    }

    public bool Authenticate()
    {}
 }

これで、IoCコンテナを介してUserオブジェクトをインスタンス化できるようになります。たとえば、Ninjectを使用している場合、構文は次のようになると思います。

var user = kernel.Get<User>(new ConstructorArgument("userName", "<actual username value>"));

Unityを使用すると、次のようになると思います。

container.Resolve<User>(new ParameterOverride("userName", "<actual username value>"))

IoCコンテナによっては、インターフェイスを実装しておらず、実際に何かと交換しようとしていない場合でも、Userクラスを登録する必要がある場合があります。登録済みのIUserRepositoryをUserクラスのコンストラクターパラメーターに接続するために必要になる場合があります。

インスタンス化コードを本当にクリーンに保ちたい場合は、Userクラスの静的メソッドを維持し、上記の1行だけを実行させることができます。

免責事項:私はこのコードをテストしていません。

于 2013-02-13T18:48:03.097 に答える