-4

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

只是想看看社区对此的看法。

这就是我问上述问题的原因:

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

4

4 回答 4

2

是的,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;
};

A如果不提供、 和的值_x,则无法创建 的实例。_y_z

于 2013-03-12T19:38:02.267 回答
2

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

  • 许多(如果不是大多数)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# 等语言中,构造函数中提供了必需的状态或依赖项,而可以通过属性设置可选状态。但是,使语言面向对象的不是属性或继承。

于 2013-03-12T19:37:13.187 回答
2

您可以使用 DDD 原则来做到这一点:创建一个具有私有默认构造函数的类,以及一个接受所需参数并验证其值的公共构造函数。如果值无效,则抛出异常,使对象无法创建。属性也可以有私有设置器而不是公共设置器。

您还可以创建一个“强制”属性并将其放在强制属性之上;并有一个机制来检查这个基于属性是否已经用属性装饰。

例子:

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; }
}
于 2013-03-12T19:37:22.503 回答
0

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

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

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

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

于 2013-03-12T19:38:23.267 回答