考虑以下课程
public class Class1
{
public int A { get; set; }
public int B { get; set; }
public int GetComplexResult()
{
return A + B;
}
}
为了使用GetComplexResult
,此类的使用者必须知道在调用方法之前设置A
和B
。如果GetComplexResult
访问许多属性以计算其结果,如果使用者未首先设置所有适当的属性,则可能导致错误的返回值。所以你可以这样编写这个类
public class Class2
{
public int A { get; set; }
public int B { get; set; }
public int GetComplexResult(int a, int b)
{
return a + b;
}
}
这样,GetComplexResult
的调用者被强制传递所有需要的值,确保正确计算预期的返回值。但是如果有许多必需的值,参数列表也会增长,这似乎也不是好的设计。它似乎也打破了将A
,B
和GetComplexResult
封装在一个类中的重点。我甚至可能试图使GetComplexResult
静态,因为它不需要类的实例来完成它的工作。我不想绕过制作一堆静态方法。
是否有术语来描述这两种不同的创建类的方法?他们似乎都有利有弊 - 有什么我不理解应该告诉我一种方式比另一种方式更好?单元测试如何影响这种选择?
答案 0 :(得分:5)
如果你使用现实世界的例子,答案就会变得更加清晰。
public class person
{
public string firstName { get; set; }
public string lastName { get; set; }
public string getFullName()
{
return firstName + " " + lastName;
}
}
实体对象的要点是它包含有关实体的信息,并且可以执行实体需要执行的操作(基于其包含的信息)。所以是的,有些情况下某些操作无法正常工作,因为实体尚未完全初始化,但这不是设计失败。如果,在现实世界中,我问你一个尚未命名的新生婴儿的全名,那也将失败。
如果某些属性对于执行其工作的实体至关重要,则可以在构造函数中初始化它们。另一种方法是使用布尔值来检查实体是否处于可以调用给定方法的状态:
while (person.hasAnotherQuestion()) {
person.answerNextQuestion();
}
答案 1 :(得分:1)
一个好的设计规则是确保所有构造函数将对象初始化为有效状态,然后所有属性设置器和方法都强制执行有效状态。这样就不会有任何对象处于无效状态。
如果A
和B
的默认值(0)不是从GetComplexResult
生成有效结果的有效状态,则应该是一个初始化{{1}的构造函数}和A
来验证一个州。
答案 2 :(得分:0)
如果永远不允许某些字段为null,那么通常会将它们作为类构造函数的参数。如果您并不总是一次获得所有必需的值,那么使用构建器类可能会有所帮助。
例如:
public Builder {
private int a;
private int b;
public Class1 create() {
// some validation logic goes here
// to make sure we have everything and
// either fill in defaults or throw an error
// if needed
return new Class1(a, b)
}
public Builder a(int val) { a = val; }
public Builder b(int val) { b = val; }
}
然后可以按如下方式使用此构建器。
Class1 obj1 = new Builder().a(5).b(6).create();
Builder builder = new Builder();
// do stuff to find value of a
builder.a(valueOfA);
// do stuff to find value of b
builder.b(valueOfB);
// do more stuff
Class1 obj2 = builder.create();
Class2 obj3 = builder.create();
此设计允许您将实体类锁定到适当的程度,同时仍允许灵活的构建过程。它还打开了通过其他实现自定义构造过程的大门,而无需更改实体类合同。