原因是在对象构造期间应该提供满足类不变量所需的状态,因此您应该提供“强制”属性的值作为构造函数参数。您的问题是基于错误的假设,即对象的特征是通过属性设置状态。这是错误的,有几个原因,其中一些是:
- 许多(如果不是大多数)OO 语言都没有属性:Java、C++、...
- 你使用的只是形式上的一个对象,它实际上是一个普通的记录,它不是非常面向对象的,就像没有方法的 C++ 结构一样(参见底部关于 setter 与方法的注释)
允许客户端创建对象的实例,这些实例仅在以后设置为强制状态的正确值,这是在调试器公司中花费大量时间的可靠方法。
让我们采取一些User
不变的方式,即必须始终设置名字和姓氏。
class User {
public User(string first, string last) { ... }
public User(string first, string last, uint age) : this(first, last) { ... }
}
// client code:
var user = new User("john", "doe");
var user2 = new User("Clint", "Eastwood", 82);
编译器确保没有人可以在不满足不变量的情况下实例化对象。
现在将其与您的方法进行比较:
class User {
public User(string first, string last) { ... }
public User(uint age) { ... }
[Mandatory] public string FirstName { get; set; }
[Mandatory] public string LastName { get; set; }
}
// client code:
var actor = new User(82); // << invalid
actor.FirstName = "Clint";
actor.LastName = "Eastwood"; // << valid
这种方法会产生更多代码,并允许您的对象在一段时间内(在<< invalid
和之间<< valid
)处于无效状态。如果某些属性设置器抛出异常怎么办?你留下了四处漂浮的破碎的对象实例。您是否希望编译器也验证 setter 中的代码不能抛出?你认为这甚至可能吗?除此之外,每个实例化User
实例的客户端都必须检查什么是强制属性,并确保设置所有这些属性。这有效地破坏了封装。
IMO,与 getter 不同,属性设置器应该很少见。我相信在这样的课程中,你不应该有 FirstName/LastName 的 setter,只有 getter。SetName(string first, string last)
相反,如果您真的想允许更改名称,则应该有一种方法。原因如下:
// lets rename actor
actor.FirstName = "John";
actor.LastName = "Wayne";
如果最后一行抛出,你会留下约翰伊斯特伍德,一个我从未听说过的演员。这actor.SetName("John", "Wayne")
不可能发生。
此外,对于您指定它们的依赖关系的属性,例如
obj.ErrorCode = 123; // imagine that error code must be != 0
obj.ErrorMsg = "foo"; // in order to be allowed to set error code
您还会为此引入属性而不是拥有obj.SetErrorInfo(123, "foo")
吗?这很明显,属性破坏了封装,因为顺序是由实现细节引起的,这与方法调用不同。
很多时候,在 C# 等语言中,构造函数中提供了必需的状态或依赖项,而可以通过属性设置可选状态。但是,使语言面向对象的不是属性或继承。