如何确保在使用前初始化某些类成员

时间:2013-03-12 19:33:18

标签: c# c++

我在考虑语言结构以及当我们在面向对象语言中讨论类和对象时,我们如何与现实世界进行比较。就像人们谈论继承一样,人们会引用父母和孩子的例子。我所知道的OO语言中没有找到的一件事,主要是C,C ++,C#,是因为它们没有将属性声明为必需的机制。我的意思是,我无法定义一个叫做人类的类,并说面,手和说眼是我班级的强制性财产。通过使用该构造,我可以强制使用我的类的任何人在使用我的类之前需要设置这些属性。如果用户忘记设置这些属性,那么我应该得到一个编译时错误。

只是想看到社区的想法。

以下是我提出上述问题的原因:

当我构建我的用户控件时,我想确保用户在使用我的控件时应在代码中设置一些属性。例如,假设我构建了一个客户用户控件,供我团队中的其他开发人员使用。我公开的一些属性是:“CustomerId”,“FirstName”,“LastName”,“Address1”,“City”,“State”和ZipCode。现在我想确保我的控件的任何消费者都应该设置“CustomerId”。使用Constructor强制设置值是一种方法,但它会抛出运行时异常以及用户如何从.cs文件调用该构造函数而不动态创建控件并将其添加到控件集合中。

4 个答案:

答案 0 :(得分:2)

原因是在对象构造期间应该提供满足类不变量所需的状态,因此您应该提供“强制”属性的值作为构造函数参数。您的问题基于错误的假设,即对象的特征在于设置具有属性的状态。出于少数原因这是错误的,其中一些原因是:

  • 很多,如果不是大多数OO语言都没有属性:Java,C ++,...
  • 你使用的只是一个正式的对象,它实际上是一个简单的记录,它不是面向对象的,与例如没有方法的C ++结构(参见底部关于setter vs方法的注释)

允许客户端创建对象的实例,这些对象只是稍后设置了正确的强制状态值,这是在调试器中花费很多时间的可靠方法。

让一些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,财产制定者应该是罕见的,不像吸气剂。我相信在这样的类中你不应该有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#等语言中,构造函数中提供了所需的状态或依赖项,而可以通过属性设置可选的状态。但是,它不是使语言面向对象的属性或继承。

答案 1 :(得分:2)

是的,C ++和C#允许这个via构造函数。

class A
{
public: 
    A(int x, int y, int z)
      : _x(x_, _y(y), _z(z) {}
private:
    int _x;
    int _y;
    int _z;
};

如果没有为A_x_y提供值,则无法创建_z的实例。

答案 2 :(得分:1)

您可以使用DDD原则执行此操作:使用私有默认构造函数创建一个类,以及接受所需参数并验证其值的公共构造函数。如果值无效,则抛出异常,以便无法创建对象。属性也可以有私有的setter而不是public setter。

您还可以创建“强制”属性,并将其置于强制属性之上;并有一个机制,根据属性是否已使用属性进行修饰来检查这一点。

示例:

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; }
}

答案 3 :(得分:0)

当然可以!只需在构造函数中使用参数来表示哪些是必需的。

public class Human
{
    public Face Face { get; set; }
    public Hand Hand { get; set; }

    public Human(Face face, Hand hand) {} etc...
}

在这种情况下,您不能使用私有构造函数,因此为了使用Human类,这些属性基本上是“必需的”。