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;
    }

但我不确定如何中止对象初始化。事实上,这可能吗?

有没有办法中止对象初始化(即:将对象设置为null)或者有更好的方法来完成这个?

4

9 回答 9

6

好吧,你只会抛出一个异常。但我根本不喜欢这种处理这个问题的方式。相反,您应该通过服务创建用户,并让服务检查名称是否有效。

于 2012-05-08T14:57:59.940 回答
4

我想您可以在对象的构造函数或名称设置器中检查并抛出异常,但是eeeeehhhh 可能会带来许多问题和混合问题。我说通过一个工厂创建对象,该工厂执行此检查并返回 null(或一个很好命名的异常)。或者创建 POCO 对象并通过单独的类/方法进行验证。

于 2012-05-08T14:57:17.460 回答
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");相应地使用和处理空返回/异常。

值得补充的是,无论您使用哪种存储用户名的方法,都应该更新以说明该名称是在 CreateUser 方法中使用的。否则,如果您在将此对象存储到数据库或其他东西之前等待一段时间,您仍然会遇到问题。我已经更新了上面的代码以使其更清晰。

于 2012-05-08T15:02:34.620 回答
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);
    }
}
于 2012-05-08T15:11:53.667 回答
2

您可能应该在创建用户之前检查重复的名称。

于 2012-05-08T14:57:54.480 回答
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根据您的要求进行所有业务逻辑检查。

于 2012-05-08T15:01:00.283 回答
0

您正在寻找的是身份映射模式,或者是这种模式。将此责任留在对象本身可能是错误的,应该在创建实体的组件中完成。当然,如果需要,地图应该是线程安全的,以避免竞争条件。

于 2012-05-08T15:02:13.577 回答
0

我将在将用户纳入集合的承诺中处理此问题。例如,如果您正在编辑一组用户,然后将它们持久化到数据库中,则持久层将负责验证。我一直认为让一个对象像它一样负责维护所有其他对象是不好的做法。当没有对象时,它会引入与对象本身的父子关系。我建议实施某种验证引擎来处理这个问题。

于 2012-05-08T15:02:30.230 回答
0

与其在对象本身内部进行此验证,不如将此实体的创建、验证和保存放在服务中。当用户名不唯一时,该服务可以抛出一个ValidationException异常,甚至可以开始一个事务以确保不会发生竞争条件。我使用的一个很好的模型是命令/处理程序模式。这是一个例子:

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);
    }
}

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

于 2012-05-08T15:08:49.623 回答