在创建必须验证条件的对象时,我一遍又一遍地提出了这个问题。应该在创建对象之前还是在对象本身的构造函数中进行检查?
为更好地说明问题,请举一个例子:假设我们有一个学生经理,一位教授,他将学生对象添加到其中。创建一个新的学生对象时,我们必须检查他的名字最长不超过20个字符。
class Professor{
LinkedList<Student> studentsList;
Professor(){
studentsList = new LinkedList<Student>();
}
public Student addStudent(String studentName){
// Place 1
if (studentName.length <= 20)
studentList.add(new Student(studentName));
else
// Do another thing
}
}
class Student {
String name;
Student(String studentName){
// Place 2
if (studentName.length <= 20)
name = studentName);
else
// Don't create the object and throw exception
}
}
所以基本上我的问题是,应该在创建学生之前在“地方1”中进行检查,还是在学生的构造函数中在“地方2”中进行检查。
答案 0 :(得分:3)
在简单程序中,没有太大关系。在复杂的应用程序中,有许多因素决定了这一点:
等...
因此,大多数情况下,这将由体系结构或设计约束或与更大应用程序相关的其他因素来给出。在非常小的程序中,您可能找不到任何确定验证最佳位置的因素。
在上面显示的示例对象创建代码中,通常不会默默地跳过超过20个字符的值,但在这种情况下通常会引发异常。如果这是数据处理,而不是故意对少于20个字符的记录进行过滤,则您不想默默地忽略不合适的记录。 (想象一下,谁将手动检查为什么在1000条记录中为什么缺少5条记录,并且没有错误消息指出出了什么问题。所以也许您可以看到,上述方法无论如何都不实用。) / p>
答案 1 :(得分:3)
通常在object-oriented programming (OOP)中,我们希望对象对自己负责。有关内部状态完整性的业务规则应在内部进行妥善处理(或委托给构建者-参见下文)。这个想法是OOP中正式称为encapsulation的一部分。
因此,在您的示例中,Professor
班级不必担心Student
班级的规则,例如学生姓名的长度。 Student
类应加强其自身的完整性。我们希望这些完整性规则的逻辑位于一个位置,而不是散布在整个应用程序中。
实际上, Professor
类应该不实例化Student
对象。在您的示例中暗含的是,必须有其他人将学生分配给教授。也许是一个Tutorial
对象,负责跟踪由教授指导的几个学生的作业和进度。该Tutorial
应该实例化Student
对象,或者传递从其他来源(例如数据库服务对象)接收到的Student
对象。
当Student
对象到达Professor
时,它们应该是有效的。 Professor
类不应关注使Student
有效的原因。 Professor
只应关注使Professor
有效的因素。
class Professor{
List< Student > students;
…
public void addStudent( Student student ){
Objects.requireNonNull( student , "Received NULL rather than a Student object. Message # 68a0ff63-8379-4e4c-850f-e4e06bd8378a." ) ; // Throw an exception if passed a null object.
Objects.requireNonNull( this.students , "Collection of Student objects is NULL. Message # c22d7b22-b450-4122-a4d6-61f92129569a." ) ; // Throw an exception if the `students` list is not established.
this.students.add( student ) ;
}
}
除了对象负责自己的想法外,Professor
不实例化Student
对象的另一个原因是为了促进testing。如果Student
对象来自其他来源,则该来源可以使用Student
类或尚未完成的接口提供 faux 对象,并且禁用了某些功能(例如数据库访问),或被为测试场景而设计的虚假数据代替。
如果您有多个需要验证才能实例化新对象的属性,则可能要使用Builder pattern。您定义了一个附加类,例如StudentBuilder
,该类具有针对学生所需的每个部分的方法。
通常,这些方法都返回相同的StudentBuilder
对象以简化call-chaining。
不同的人对于建筑商有不同的风格。一种方法是提供一种有效性检查方法,也许是一种提供一系列无法构建所需对象的问题的方法。
有些人使用with
之类的词而不是访问器方法set
来表明,虽然我们暂时在构建器上设置属性,但真正的目的是在另一个类的对象。
StudentBuilder sb = new StudentBuilder().withFirstName( "Alice" ).withLastName( "Coleman" ).withEmail( "x@y.com" );
if( sb.isValid() ) {
Student s = sb.build() ;
…
}