2012-05-08 10 views
7

私は常に一定の値を持たなければならないオブジェクトを作成しています。特に、それは常にNameプロパティの値を持たなければなりません。オブジェクトの初期化を中止するにはどうすればよいですか?

public class User 
{ 
    public string Name { get; set; } 

    public User(string name) 
    { 
     Name = name; 
    } 
} 

ここでは、このクラスで実装する必要があるビジネスルールがいくつかあります。その1つは、Nameプロパティが一意の名前でなければならないということです。だから、私はこのオブジェクトのイニシャライザが何かこのように見えると思います:

public User(string name, IQueryable<User> allUsers) 
    { 
     var matches = allUsers.Where(q => q.Name == name).ToList(); 
     if(matches.Any()) 
     { 
      // abort object initialization 
     } 
     Name = name; 
    } 

しかし、私はどのように私はオブジェクトの初期化を中止するか分からない。実際、それも可能ですか?

オブジェクトの初期化を中止する方法はありますか(つまり、オブジェクトをnullに設定する)か、これを行うにはより良い方法がありますか?

+0

強引な方法で、オブジェクト – RhysW

+2

を削除これは単なるサンプルコードであってもよいが、場合には、それはない、ヌルフィールドを持つオブジェクトを追加し、nullにすべてのフィールドを設定します。あなたはどれ 'に述語を供給することができます'だから、あなたは' Where'を通過する必要はありません。 –

+0

@BrianRasmussenどちらもまったく同じ結果になるので、あなたが使う個人的な好みです。一方、 'ToList'呼び出しは' Any'の短絡がクエリ全体を評価しないようにします。 – Servy

答えて

3

オブジェクトの初期化を中止するには、コンストラクタに例外をスローします。無効な入力を拒否することをお勧めします。

public class User 
{ 
    public User(String name) { 
     if (String.IsNullOrWhiteSpace(name)) { 
      if (name == null) { 
       throw new System.ArgumentNullException("Cannot be null.", "name"); 
      } 
      else { 
       throw new System.ArgumentException("Cannot be empty.", "name"); 
      } 
     } 
    } 
} 

コンストラクタで定義したいビジネスロジックは、そこに収まりません。コンストラクターは、軽量でインスタンス化する必要があります。一部のデータソースを照会するのは、コンストラクターにとって高価すぎます。このため、代わりにファクトリパターンを使用する必要があります。ファクトリパターンを使用すると、呼び出し元はオブジェクト作成に伴う重労働が予想されることがあります。

public class User 
{ 
    private User(String name) { 
     if (String.IsNullOrWhiteSpace(name)) { 
      if (name == null) { 
       throw new System.ArgumentNullException("Cannot be null.", "name"); 
      } 
      else { 
       throw new System.ArgumentException("Cannot be empty.", "name"); 
      } 
     } 
    } 

    public static User CreateUser(String name) { 
     User user = new User(name); // Lightweight instantiation, basic validation 

     var matches = allUsers.Where(q => q.Name == name).ToList(); 

     if(matches.Any())   
     {   
      throw new System.ArgumentException("User with the specified name already exists.", "name");   
     }  

     Name = name; 
    } 

    public String Name { 
     get; 
     private set; // Optionally public if needed 
    } 
} 

あなたは工場出荷時のパターンは、より良いフィット、それは方法だから呼び出し側はそれを呼び出すことによって起こっていくつかの仕事があると期待するかもしれないことがわかります。コンストラクタを使用すると、軽量であると予想されます。

コンストラクタルートに移動する場合は、データソースへの実際の挿入が試行されたときなど、ビジネスルールを強制する他の方法を試してみることをお勧めします。

public class User 
{ 
    public User(String name) { 
     if (String.IsNullOrWhiteSpace(name)) { 
      if (name == null) { 
       throw new System.ArgumentNullException("Cannot be null.", "name"); 
      } 
      else { 
       throw new System.ArgumentException("Cannot be empty.", "name"); 
      } 
     } 
    } 
} 

public class SomeDataSource { 
    public void AddUser(User user) { 
     // Do your business validation here, and either throw or possibly return a value 
     // If business rules pass, then add the user 
     Users.Add(user); 
    } 
} 
+0

このような詳細な回答をありがとうございました!私は、あなたが概説したファクトリーパターンのルートに行くように説得されました。 – quakkels

+0

大歓迎です、私が手伝ってくれてうれしいです。 –

4

オブジェクトのコンストラクタまたはName setterで例外をチェックしてスローすることができますが、多数の問題と混在する懸念があるeeeehhhhがあるとします。私は、このチェックを行うファクトリを通じてオブジェクトを作成し、null(またはきちんと名前付きの例外)を返します。または、POCOオブジェクトを作成し、別のクラス/メソッドを使用して検証を行います。

2

ユーザーを作成する前に、重複している名前を確認する必要があります。

+1

競合状態になる可能性があります。 – jason

+0

あなたは常に競合条件を得ることができます:-) – Steven

6

さて、あなたはただ例外をスローします。しかし、私はこの問題を全く処理するこの方法が嫌いです。むしろ、サービスを使用してユーザーを作成し、その名前が有効かどうかをサービスで確認する必要があります。

2

個人的には、インスタンス化する前に論理チェックを実行します。たとえば、

if(UserLogic.PreInsertValidation(string username)){ 
    User newUser = new User(username); 
} 
else{ 
    // Handling - maybe show message on client "The username is already in use." 
} 

PreInsertValidationは、要件に基づいてすべてのビジネスロジックチェックを行います。

0

あなたが探しているものは、identity mapパターン、またはこれのようなものです。この責任をオブジェクト自体に残すことは間違いです。エンティティを作成するコンポーネントで行う必要があります。もちろん、マップは競争条件を避けるために、必要に応じてスレッドセーフである必要があります。

0

私はこれを、コレクション内のユーザーのコミットで処理します。たとえば、ユーザーのコレクションを編集してからそれらをデータベースに永続化する場合、永続性レイヤーによって検証が行われます。私は常にオブジェクトが他のすべてのオブジェクトをそのように維持する責任を負うことを悪い習慣とみなしてきました。存在しない場合は、オブジェクト自体との親子関係を導入します。これを処理するための検証エンジンを実装することをお勧めします。

public static User CreateUser(string name) 
{ 
     // Check whether user already exists, if so, throw exception/return null 

     // If name didn't exist, record that this user name is now taken. 
     // Construct and return the user object 
     return new User(name); 
} 

private User(string name) 
{ 
     this.Name = name; 
} 

次に、あなたの呼び出し元のコードがUser myUser = User.CreateUser("Steve");を使用することができますし、それに応じてnullを返却/例外を処理:代わりにpublicコンストラクタを持つことの

3

は、このような方法とプライベートコンストラクタを持っています。

どのユーザー名を保存するかを使用しているどのメソッドでも、この名前がCreateUserメソッド内で使用されるように更新する必要があります。それ以外の場合、このオブジェクトをデータベースなどに格納する前にしばらく待っても問題は残ります。私はこれを明確にするために上記のコードを更新しました。

+1

この方法では、静的な 'IQueryable'を使用してすべてのユーザー(またはすべてのユーザーリポジトリへの参照)を保持し、ロックする競合条件を回避できます。 – SWeko

+0

@SWekoこのメソッドとコンストラクターを使用すると、ロックがない場合は競合条件が発生し、両方とも適切なロックを追加することによって競合条件を削除できます。 – Servy

0

オブジェクト内でこの検証を実行する代わりに、このエンティティの作成、検証、および保存をサービスに入れます。このサービスは、ユーザー名が一意でない場合はValidationExceptionを投げることができ、競合状態が発生しないようにトランザクションを開始することさえできます。私が使用する良いモデルはcommand/handlerパターンです。ここに例があります:

public class CreateNewUserCommand 
{ 
    public string UserName { get; set; } 
} 

internal class CreateNewUserCommandHandler 
    : ICommandHandler<CreateNewUserCommand> 
{ 
    private readonly IUnitOfWork uow; 

    public CreateNewUserCommandHandler(
     IUnitOfWork uow) 
    { 
     this.uow = uow; 
    } 

    public void Handle(CreateNewUserCommand command) 
    { 
     // TODO Validation 

     var user = new User { Name = command.Name }; 

     this.uow.Users.InsertOnSubmit(user); 
    } 
} 

さらに、独自のクラスに検証を追加することもできます。

関連する問題