假设您有以下两种方法:

第1名:

void AddPerson(Person person)
{
  // Validate person
  if(person.Name != null && IsValidDate(person.BirthDate)
    DB.AddPersonToDatabase(person);
}

2号:

void AddPerson(string name, DateTime birthDate)
{
  Person p = new Person(name, birthDate);
  DB.AddPersonToDatabase(person);
}

这两种方法中哪一种最好?我知道第一个更正确OO-wise,但我觉得第二个更可读,并且你不必确保对象是有效的,因为参数确保这一点。我只是不喜欢在传递它们作为参数的任何地方验证对象。还有其他方法吗?

编辑: 感谢所有答案。为了澄清,在构造函数和IsValid方法中进行验证当然是一种很好的方法,但在我的代码中,人的有效状态通常取决于上下文,并且可能因方法而异。这当然可能是糟糕设计的标志。

代码只是描述问题的一个例子。

有帮助吗?

解决方案

第一个不应该验证person.Name和person.BirthDate - 它们应该由 Person 构造函数自动验证。换句话说,如果你传递了一个人,你应该知道它是有效的。

另一方面,您必须检查 person 是否为空引用。

有时值得使用第二个版本之类的便捷方法,以避免必须经常显式调用构造函数。它通常应该只调用 Person 构造函数,然后将工作委托给第一个表单。

其他提示

第一个优点是允许您在不破坏现有代码的情况下更改Person定义,只需要重新编译。您可能认为第二个更具可读性,但第一个更易于维护,您的选择。

另一种选择是:

void AddPerson(Person person)
{  // Validate person  
   if(person.IsValid)
   {
     DB.AddPersonToDatabase(person);
   }
}

假设Person在构造时不自行验证。如果对象在瞬态时可能是无效状态,则在某些情况下是可行的。

我更喜欢前者(传递一个对象),因为它减少了API与对象的耦合。如果您更改 Person 对象,例如添加以前不需要的新属性,如 Nickname ,然后在第一种情况下,您不需要更改公共API,而在第二种情况下,您需要更改方法或添加一个新的重载。

我同意这完全取决于背景,对此没有绝对的规则。在我看来,拥有这样的方法是无稽之谈:

person.SetBirthDate(Person person)
person.ResetPassword(Person person)

但在这种情况下,我更喜欢前者,因为正如Greg Beech所说,该方法不必(必须)了解域对象的任何信息。

顺便说一下,考虑超载(DRY - 不要重复自己):

void AddPerson(Person person)
{
  if(person.Name != null && IsValidDate(person.BirthDate)
    DB.AddPersonToDatabase(person);
}

void AddPerson(string name, DateTime birthDate)
{
  Person p = new Person(name, birthDate);
  this.AddPerson(p);
}

最好传递一个Person对象,而不是一堆原始类型作为参数。比较以下两种方法


public static void Withdrawal(Account account, decimal amount)
{
    DB.UpdateBalance(account.AccountNumber, amount);
}

public static void Withdraw(int accountNumber, decimal amount)
{
    DB.UpdateBalance(accountNumber, amount);
}

这两种方法几乎完全相同,但第二种方法不安全。一个int可以来自任何地方,所以如果你写这个就搞砸了:

private void CloseTransaction(Transaction tran)
{
    BankAccounts.Withdrawal(tran.Account.RoutingNumber, tran.Amount);
        // logic error: meant to pass Account.AccountNumber instead of Account.RoutingNumber
}

这是最糟糕的错误,因为它不会抛出编译错误或运行时异常。如果你写得足够好,你可能会在自动化测试中发现这个错误,但这个错误很容易被遗漏,并且可能隐藏数月而不被发现。

我在一家编写银行软件的公司工作,我们确实在生产中遇到过这种类型的错误。它只发生在特定类型的金库转移期间,只有在我们的一家银行每次运行月末流程时发现其GL余额少了100美元时才会发现它。该银行怀疑员工被盗数月,但只有通过仔细的代码审查,才有人将问题追溯到我们软件中的错误。

我大部分时间都会去第一个。结果

第二个会破坏Person中每个更改的签名

这取决于具体情况。

如果所有调用方法都处理Person对象,那么第一个是正确的解决方案。

但是,如果你的一些调用方法处理的是name和birthdates,而且有些处理Person对象,那么第二个例子就是正确的解决方案。

我只会为两者创建重载。特别是考虑到它们可以自动创建。

我假设这些方法不是 Person 类型的一部分。考虑到这一点,我觉得他们都对另一种类型( Person )imo有太多的了解。第一个版本不应该验证有效人员实例的创建。如果这是调用者的责任,那么每个调用者都必须这样做。这很脆弱。

第二个版本对另一个Type有很强的依赖性,因为它创建了这种类型的实例。

我肯定更喜欢第一个版本,但我会从代码中移动验证部分。

我认为乔恩几乎已经钉了它。 Person 应该负责确保为其构造函数创建一个有效的Person。

关于创建或不创建 Person 对象(无论是AddPerson方法还是其调用方)的resonsibility,请阅读

http://en.wikipedia.org/wiki/GRASP_%28Object_Oriented_Design %29#创建者

这是关于OOP的责任问题。在您的特定情况下,如果AddPerson将调用包装到数据库接口,我不太确定。它取决于Person对象在该上下文之外的用途。如果它仅仅是为了包含要添加到数据库的数据,那么在AddPerson方法中创建它可能是一个好主意,因为它将您的类的用户与必须知道Person类分离。 。

如果使用对象,可以更好地进行封装;这就是他们的目的。我认为人们在使用原语时会遇到麻烦。

不应该创建无效的人。构造函数应该检查有效参数并抛出IllegalArgumentException或者如果它们无效则失败。这就是“按合同编程”的全部内容。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top