2012-05-08 95 views
7

我正在寫一個必須始終具有某些值的對象。最值得注意的是,它必須始終具有Name屬性的值。如何中止對象初始化?

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

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

現在,我有一些業務規則需要在這個類中實現。其中之一是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; 
    } 

但我不知道我怎麼會放棄對象的初始化。事實上,這甚至有可能嗎?

有沒有辦法中止對象初始化(即:設置對象爲空)還是有更好的方法來完成這個?

+0

蠻力的方式,將所有字段設置爲null,與空字段添加對象,刪除對象 – RhysW

+2

這可能只是示例代碼,但如果它不是:你可以提供一個謂詞任何' ',所以你不必經過'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

我想你可以在對象的構造函數或名稱設置器中檢查並拋出一個異常,但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

我會在集合中提交用戶的提交中處理這個問題。例如,如果您正在編輯一組用戶,然後將它們保存到數據庫中,則持久層將負責驗證。我一直認爲讓一個對象負責維護所有其他對象是一種不好的做法。當沒有任何東西時,它會引入與對象本身的父子關係。我建議使用某種驗證引擎來處理這個問題。

3

取代具有公共構造的,有這樣的方法和私人構造函數:

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 /異常。

值得一提的是,無論您使用哪種存儲哪些用戶名的方法,都應該更新,以表明該名稱是在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); 
    } 
} 

你甚至可以將驗證額外添加到它自己的類中。