调用者应该在调用构造函数之前检查参数的有效性吗?

时间:2013-03-11 11:31:53

标签: java oop coding-style

我最近已经阅读了很多关于TDD和清理代码的内容,所以我开始研究一个简单的项目来使用这些代码,我遇到了一些我真的不确定最好的方法是什么。< / p>

我有一个以Java File对象作为参数的类,期望这个File对象必须是一个目录,并且必须以某个前缀开头。我的第一次传递涉及在调用构造函数之前对File对象进行检查,即检查它是否是目录并检查名称是否有效。但是我不喜欢调用者正在指定什么使它有效,特别是有效前缀是什么,我认为这个逻辑应该放在类本身。

我可以在构造函数中执行此检查,如果它无效则抛出异常,但考虑到问题的性质,如果我在File列表上进行迭代,那么完全可以预期一些它们不会是“有效的”(即它们将是文件而不是目录)所以真的有必要抛出Exception吗?

public MyObject(File directory) {
    if (!directory.isDirectory()) {
        throw new IllegalArgumentException("Must be a directory");
    }
    if (!directory.getName().startsWith("Prefix")) {
        throw new IllegalArgumentException("Must start with Prefix");
    }
    ....
}

我想过可能会添加一个Factory方法来创建对象,如果File无效,则返回null。

public static MyObject createMyObject(File directory) {
    if (!directory.isDirectory() || !directory.getName().startsWith("Prefix")) {
        return null;
    }
    return new MyObject(directory);
}

或者我考虑在调用构造函数之前为调用者验证File的类添加静态方法。

public static boolean isValid(File directory) {
    return directory.isDirectory() && directory.getName().startsWith("Prefix");
}

if (MyObject.isValid(directory)) {
    MyObject object = new MyObject(directory);
}

那么在清洁代码和所有OOP原则(例如单一责任,耦合等)方面,这将是首选方式吗?

更新

在阅读了已经发布的一些答案之后,我开始考虑另一种可能性,这种可能性仅适用于我目前的情况而非一般情况,因为我的问题确实存在。

作为我的调用代码的一部分,我有一个来自文件系统的路径,我列出了该目录中的所有文件,然后我将传递给MyObject构造函数的每个文件是否有效。我可以将FileFilter传递给方法listFiles,以确保listFiles仅返回有效目录。可以在MyObject中声明FileFilter

public static FileFilter getFilter() {
    return new FileFilter() {
        public boolean accept(File path) {
            return path.isDirectory() && path.getName().startsWith("Prefix");
        }
    };
}

如果我的构造函数抛出异常,那么它实际上是一种特殊情况,因为期望它只是传递有效目录。这样做意味着我可以从构造函数/工厂中删除对已检查异常的需要,因为任何异常都会在某处显示错误而不是预期的行为。但它仍然存在是否将其置于构造函数或工厂方法中的问题。

5 个答案:

答案 0 :(得分:3)

public static MyObject createMyObject(File directory) throws IllegalArgumentException{
    if (!directory.isDirectory() || !directory.getName().startsWith("Prefix")) {
        return throw new IllegalArgumentException("invalid parameters")";
    }
    return new MyObject(directory);
}

这是一个选项,它是两个建议选项的混合。使用工厂方法并验证工厂方法擅长的前提条件。所以IMO这可能是个不错的选择。

  

为什么不选择选项2:   因为当您可以抛出异常以警告用户某些前提条件未得到满足时,返回nulls是一个不好的选择。

<强>更新 此策略保证仅创建处于有效状态的对象。 此外,如果你想模拟MyObject的实例,你可以使它实现一些使用Runtime Polymorphism的接口并传递模拟对象。希望这是有道理的。

答案 1 :(得分:1)

我认为验证代码所属的位置取决于'MyObject'类所代表的确切内容。

如果MyObject执行某些操作,如果它有一个文件而不是一个目录就会失败,那么我会说它应该在其构造函数中包含验证代码 - 这使得该类自包含,并允许可能的重用以后的课程。

如果MyObject只是文件/目录的容器,并且其中没有特定于目录的代码,那么将验证代码放在该所需的类中,需要一个目录而不是一个文件

答案 2 :(得分:1)

我倾向于提供一个构造函数的组合,当给定的参数没有满足由static boolean isValid()验证方法补充的合同时,它会验证并抛出异常。

在循环中使用验证方法提供了一种很好的,可读的构造有效对象的方法,而构造函数确保在需要时抛出异常来处理未经验证的用法。

答案 3 :(得分:0)

这取决于......

从编码的角度来看,最简单,最干净的方法是爆炸式(unchdcked)构造函数。如果有一个合理的期望,调用者只会传入目录,那么请继续使用。

如果有合理的期望调用者可以传入非目录,那么您有两种选择:

  1. 如果调用者可以处理这种情况,请让构造函数抛出检查异常
  2. 如果调用者无法处理异常,那么如果调用者调用一个方法,你可以让你的类“什么也不做” - 如果传递给构造函数的File不是一个目录,基本上忽略所有“do”的请求。

答案 4 :(得分:0)

这称为Design-by-Contract,并且有一些库可以帮助您检查传入的参数。