2012-05-08 70 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); 
    } 
} 

你甚至可以将验证额外添加到它自己的类中。