如何确保某些类成员在使用前被初始化
How to ensure certain class members are initialized before use
我在考虑语言结构,以及当我们谈论面向对象语言中的类和对象时,我们如何与现实世界进行比较。就像当人们谈到继承时,人们会引用父母和孩子的例子。在我所知道的面向对象语言(主要是C、c++和c#)中,我没有发现一件事,那就是它们没有将属性声明为强制的机制。我的意思是我不能定义一个叫做人类的类然后说脸,手,眼睛是我类的强制属性。通过这个构造,我可以强制任何使用我的类的人在使用我的类之前设置这些属性。如果用户忘记设置这些属性,那么我应该得到一个编译时错误。
只是想看看社区对此的看法。
以下是我问上述问题的原因:
当我构建我的用户控件时,我想确保用户在使用我的控件时应该在他们的代码中设置一些属性。例如,假设我构建了一个客户用户控件,该控件将由我团队中的其他开发人员使用。我已经暴露的一些属性是:"CustomerId","FirstName","LastName","Address1","City","State"和ZipCode。现在我想确保我的控制的任何消费者应该设置"CustomerId"。使用构造函数强制设置值是一种方法,但它会抛出运行时异常,以及用户如何从.cs文件调用构造函数而不动态创建控件并将其添加到控件集合。
您可以这样做,使用DDD原则:创建一个具有私有默认构造函数的类,以及一个接受所需参数并验证其值的公共构造函数。如果值无效,则抛出异常,使对象无法创建。属性也可以有私有setter而不是公共setter。
你也可以创建一个'Mandatory'属性,并把它放在强制属性的顶部;并且要有一种机制,根据属性是否被该属性装饰来检查。
的例子:
public class BlogEntry
{
private BlogEntry() {}
public BlogEntry(string title, string body)
{
LastModifiedDate = DateTime.Now;
Title = title;
Body = body;
var blogEntryValidator = new BlogEntryValidator();
blogEntryValidator.ValidateAndThrow(this);
}
public int Id { get; private set; }
public string Title { get; private set; }
public string Body { get; private set; }
public DateTime? LastPublishDate { get; private set; }
public DateTime LastModifiedDate { get; private set; }
public virtual ICollection<Comment> Comments { get; private set; }
public void Publish()
{
LastPublishDate = DateTime.Now;
}
public void Unpublish()
{
LastPublishDate = null;
}
public void Modify(string title, string body)
{
Title = title;
Body = body;
LastModifiedDate = DateTime.Now;
}
public Comment AddComment(string commentText, string emailAddress, string name)
{
var comment = new Comment(this, commentText, emailAddress, name);
if (Comments == null) Comments = new List<Comment>();
Comments.Add(comment);
return comment;
}
public void RemoveComment(Comment comment)
{
Comments.Remove(comment);
}
}
public class Comment
{
private Comment() {}
public Comment(BlogEntry blogEntry, string name, string emailAddress, string commentText)
{
BlogEntry = blogEntry;
Name = name;
EmailAddress = emailAddress;
CommentText = commentText;
DateWritten = DateTime.Now;
var commentValidator = new CommentValidator();
commentValidator.ValidateAndThrow(this);
}
public int Id { get; private set; }
public string Name { get; private set; }
public string EmailAddress { get; private set; }
public string CommentText { get; private set; }
public DateTime DateWritten { get; private set; }
public BlogEntry BlogEntry { get; private set; }
}
是的,c++和c#允许通过构造函数实现这一点。
class A
{
public:
A(int x, int y, int z)
: _x(x_, _y(y), _z(z) {}
private:
int _x;
int _y;
int _z;
};
如果不提供_x
、_y
和_z
的值,则不能创建A
的实例
原因是实现类不变量所需的状态应该在对象构造期间提供,因此您应该提供'强制'属性的值作为构造函数参数。你的问题是基于一个错误的假设,即一个对象的特征是设置具有属性的状态。这是错误的,原因有很多,其中一些是:
- 许多,如果不是大多数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
实例的客户端都必须检查哪些是强制属性,并确保设置所有这些属性。这有效地破坏了封装。
在我看来,属性setter应该很少,不像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#等语言中,必需状态或依赖关系是在构造函数中提供的,而可选状态可以通过属性设置。然而,使语言成为面向对象的并不是属性或继承。
你当然可以!只需在构造函数中使用参数来表示哪些是强制的。
public class Human
{
public Face Face { get; set; }
public Hand Hand { get; set; }
public Human(Face face, Hand hand) {} etc...
}
在本例中,您不能使用私有构造函数,因此为了使用Human类,这些属性本质上是"强制性的"。
- C++成员初始化
- c++构造函数成员初始化:传递参数
- C++正确的指针成员初始化
- 将另一个类的对象传递到当前类C++的构造函数中(不是成员初始化)
- WinLamb 错误:成员初始化非法
- 使用其他成员初始化结构的成员?
- C++模板类静态成员初始化
- 解释了构造函数成员初始化列表
- 如何在成员初始化列表中声明共享指针
- C++入门5版:使用get成员初始化另一个与shared_ptr无关的对象
- C++11 默认类成员初始化与初始值设定项列表同时
- 调用非默认构造函数作为成员初始化
- C++模板成员初始化:用右值移动构造,但用左值移动引用
- 类成员初始化C++
- 在成员初始化列表中,我可以创建对列表中不在列表中的成员变量的引用
- C :(不重复)积分静态成员初始化(不仅是声明!),导致链接器错误,原因
- 如何调用成员初始化器列表中参考成员的构造函数
- C 构造函数采用成员初始化器
- 与其他静态const成员初始化静态常量成员
- 静态内联成员初始化顺序