Java中comprobations应该放在哪里?

3
我在创建必须验证条件的对象时,一遍又一遍地遇到了这个问题。应该在尝试创建对象之前放置检查,还是在对象构造函数中进行检查呢?
为了更好地说明这个问题,举个例子:假设我们有一个学生管理器,由一位教授负责,他将学生对象添加到列表中。创建新的学生对象时,我们必须检查他的名字是否最多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"进行检查。


2
由你决定,没有正确答案。 - shmosel
1
我必须不同意shmosel的观点。如果要求是防止名字超过20个字符的学生,则示例一是错误的答案。它只能在给定的示例中防止创建这样的学生,但并不能完全防止。 - Tom
@Tom,示例2也不行,因为可能有另一个没有检查的构造函数。而且name可以在构建后修改。我们只能根据提供的代码来回答问题。 - shmosel
@shmosel,您是在就约束验证问题辩护示例1的概念问题时,通过代码风格问题来与OP的示例2争论吗? - Tom
@Viktor,“comprobations”不是英语单词,但我认为你的意思是“validations”。 - Calicoder
显示剩余7条评论
2个回答

3
在简单的程序中,这并不太重要。在复杂的应用程序中,有许多因素决定了这一点:
  • 在某些情况下,是否仍然可以存在具有无效值的对象?(即使它们包含无效值,它们是否仍然具有意义?)
  • 验证是否昂贵?(是否需要计算、网络连接或数据库操作?)
  • 验证是否可能?(我们是否已经拥有所有所需的验证信息?)
  • 验证是否有自己的阶段,在该阶段中,该对象与其他对象一起进行验证或相互验证?
  • 是否存在现有的惯例或要求?

等等...

因此,大多数时候,这将由架构或设计约束或与更大的应用程序相关的其他因素确定。在非常小的程序中,您可能找不到任何这些因素来确定验证的最佳位置。

在上面展示的样本对象创建代码中,通常不会默默地跳过长度超过20个字符的值,而是在这种情况下通常会抛出异常。如果这是数据处理而不是故意过滤比20个字符短的记录,则不希望默默地省略不合适的记录。(想象一下,谁会手动检查为什么在1000条记录集中缺少了5条记录,而没有错误消息指示出了什么问题。因此,也许您可以看到,上述方法无论如何都不适用于实际使用。)


3

对象自我负责

通常在面向对象编程(OOP)中,我们希望对象能够自我负责。关于其内部状态完整性的业务规则应该在内部处理(或委托给构建器--见下文)。这个想法是面向对象编程中正式称为封装的一部分。

因此,在您的示例中,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 对象的另一个原因是为了方便 测试。如果 Student 对象来自其他来源,则该来源可以使用尚未完成、某些功能已禁用(如数据库访问)或被替换为设计用于测试场景的伪数据的 Student 类或接口提供虚假对象。

生成器模式

如果您有多个属性需要验证才能实例化新对象,则可能希望使用 生成器模式。您定义一个额外的类,例如 StudentBuilder,它具有创建学生所需的每个部分的方法。

通常,这些方法都返回相同的 StudentBuilder 对象,以便方便 调用链接

不同的人对于构建者有不同的风格。其中一种方法是提供一个有效性检查方法,以及可能提供一个列出阻止构建所需对象的问题的方法。
有些人使用像with这样的单词,而不是访问器方法set,以明确表示虽然我们在临时设置构建器上的属性,但真正的意图是在另一个类的对象上设置属性。
StudentBuilder sb = new StudentBuilder().withFirstName( "Alice" ).withLastName( "Coleman" ).withEmail( "x@y.com" );
if( sb.isValid() ) {
    Student s = sb.build() ;
    …
}

直到现在我都不确定为什么之前从未听说过 java.util.Objects 类。但话说回来,我最后一个主要的 Java 项目只使用了 Java 6... - Powerlord

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接