假设您有以下两种方法:
第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方法中进行验证当然是一种很好的方法,但在我的代码中,人的有效状态通常取决于上下文,并且可能因方法而异。这当然可能是糟糕设计的标志。
代码只是描述问题的一个例子。
答案 0 :(得分:10)
第一个不应该验证person.Name和person.BirthDate - 它们应该由Person
构造函数自动验证。换句话说,如果你传递了一个人,你应该知道它是有效的。
另一方面,您必须检查person
是否为空引用。
有时值得使用第二个版本之类的便捷方法,以避免必须经常显式调用构造函数。它通常应该只调用Person
构造函数,然后将工作委托给第一个表单。
答案 1 :(得分:2)
第一个优点是允许您在不破坏现有代码的情况下更改Person定义,只需要重新编译。您可能认为第二个更具可读性,但第一个更易于维护,您的选择。
答案 2 :(得分:2)
另一种选择是:
void AddPerson(Person person)
{ // Validate person
if(person.IsValid)
{
DB.AddPersonToDatabase(person);
}
}
假设Person在构造时不自行验证。如果对象在瞬态时可能是无效状态,则在某些情况下是可行的。
答案 3 :(得分:1)
我更喜欢前者(传递对象),因为它减少了API与对象的耦合。如果您更改Person
对象,例如添加以前不需要的新属性,例如Nickname
,然后在第一种情况下,您不需要更改公共API,而在第二种情况下,您需要更改方法或添加新的过载。
答案 4 :(得分:1)
我同意这完全取决于背景,对此没有绝对的规则。在我看来,拥有这样的方法是无稽之谈:
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);
}
答案 5 :(得分:1)
最好传递一个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美元时才会发现它。该银行怀疑员工被盗数月,但只有通过仔细的代码审查,才有人将问题追溯到我们软件中的错误。
答案 6 :(得分:0)
我大部分时间都会去第一个。
第二个会破坏Person中每个更改的签名
答案 7 :(得分:0)
这取决于具体情况。
如果所有调用方法都处理Person对象,那么第一个是正确的解决方案。
但是,如果你的一些调用方法处理的是name和birthdates,而且有些处理Person对象,那么第二个例子就是正确的解决方案。
答案 8 :(得分:0)
我只会为两者创建重载。特别是考虑到它们可以自动创建。
答案 9 :(得分:0)
我假设这些方法不属于Person
类型。考虑到这一点,我觉得他们都对另一种类型(Person
)imo有太多的了解。第一个版本不应该验证有效人员实例的创建。如果这是调用者的责任,那么每个调用者都必须这样做。这很脆弱。
第二个版本对另一个Type有很强的依赖性,因为它创建了这种类型的实例。
我肯定更喜欢第一个版本,但我会从代码中移动验证部分。
答案 10 :(得分:0)
我认为乔恩几乎已经钉了它。 Person
应该负责确保有效的Person被创建为它的构造函数。
关于创建或不创建Person
对象(无论是AddPerson方法还是其调用方)的resonsibility,请阅读
http://en.wikipedia.org/wiki/GRASP_%28Object_Oriented_Design%29#Creator
这是关于OOP的责任问题。在您的特定情况下,如果AddPerson将调用包装到数据库接口,我不太确定。它取决于Person对象在该上下文之外的用途。如果它仅仅是为了包含要添加到数据库的数据,那么在AddPerson方法中创建它可能是一个好主意,因为它将您的类的用户与必须知道Person类分离。 。
答案 11 :(得分:0)
如果使用对象,可以更好地进行封装;这就是他们的目的。我认为人们在使用原语时会遇到麻烦。
不应该创建无效的人。构造函数应该检查有效参数并抛出IllegalArgumentException或者如果它们无效则失败。这就是“按合同编程”的全部内容。