Java 调用方应该在调用构造函数之前检查参数的有效性吗?
我最近读了很多关于TDD和clean代码的书,所以我开始做一个简单的项目来使用它们,我遇到了一些我真的不确定最好的方法是什么 我有一个类,它将一个JavaJava 调用方应该在调用构造函数之前检查参数的有效性吗?,java,oop,coding-style,Java,Oop,Coding Style,我最近读了很多关于TDD和clean代码的书,所以我开始做一个简单的项目来使用它们,我遇到了一些我真的不确定最好的方法是什么 我有一个类,它将一个Java文件对象作为参数,期望这个文件对象必须是一个目录,并且必须以某个前缀开头。我的第一个步骤是在调用构造函数之前检查文件对象,即检查它是否是目录,并检查名称是否有效。但是我不喜欢调用方指定什么使它有效,特别是有效前缀是什么,我认为这个逻辑应该放在类本身中 我可以在构造函数中执行此检查,如果异常无效,则抛出异常,但鉴于问题的性质,如果我在一个文件的列
文件
对象作为参数,期望这个文件
对象必须是一个目录,并且必须以某个前缀开头。我的第一个步骤是在调用构造函数之前检查文件
对象,即检查它是否是目录,并检查名称是否有效。但是我不喜欢调用方指定什么使它有效,特别是有效前缀是什么,我认为这个逻辑应该放在类本身中
我可以在构造函数中执行此检查,如果异常无效,则抛出异常,但鉴于问题的性质,如果我在一个文件的列表中迭代,则完全可以预期其中一些将不“有效”(即,它们将是文件而不是目录)那么抛出一个异常
真的有必要吗
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");
}
....
}
我考虑添加一个工厂方法来创建对象,如果文件
无效,则返回null
public static MyObject createMyObject(File directory) {
if (!directory.isDirectory() || !directory.getName().startsWith("Prefix")) {
return null;
}
return new MyObject(directory);
}
或者,我考虑向类添加一个静态方法,在调用构造函数之前为调用方验证文件
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只返回有效的目录。FileFilter
可以在MyObject中声明:
public static FileFilter getFilter() {
return new FileFilter() {
public boolean accept(File path) {
return path.isDirectory() && path.getName().startsWith("Prefix");
}
};
}
如果我的构造函数抛出了一个异常,那么这将是一个真正的异常情况,因为我们期望它只被传递给有效的目录。这样做意味着我可以从构造函数/工厂中删除检查异常的需要,因为任何异常都会指示某个地方的错误,而不是预期的行为。但它仍然留下了一个问题:是将它放在构造函数中还是放在工厂方法中
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);
}
这是一个选项,它是您建议的两个选项的组合。使用工厂方法,并验证工厂方法擅长的前提条件。所以在我看来,这是一个不错的选择
为什么不按原样选择2:
因为当您可以抛出异常以警告用户某些先决条件未满足时,返回null
是一个坏选项
更新:
此策略保证只创建处于有效状态的对象。
另外,如果您想模拟MyObject
的实例,您可以让它实现一些使用运行时多态性的接口,并传递模拟对象。希望这是有意义的。我认为验证代码的归属完全取决于“MyObject”类所代表的内容
如果MyObject执行一些操作,如果它有一个文件而不是一个目录,那么它应该在其构造函数中包含验证代码——这使得类是自包含的,并且允许以后可能重新使用该类
如果MyObject只是文件/目录的容器,并且其中没有特定于目录的代码,然后将验证代码放在确实需要目录而不是文件的类中。我倾向于提供一个构造函数的组合,该构造函数在给定的参数未满足其契约时进行验证并引发异常,并辅以静态布尔值isValid()
验证方法
在循环中使用验证方法提供了一种构造有效对象的好方法,可读性强,而构造函数则确保在需要时通过抛出异常来处理未经验证的使用。这取决于
从编码的角度来看,最简单、最干净的方法是分解(undcked)构造函数。如果有一个合理的预期,调用方将并且应该只传入目录,那么就这样做
如果有一个合理的预期,调用方可能会在非目录中传递,那么您有两个选择:
如果调用方能够处理这种情况,则让构造函数抛出一个已检查的异常
如果调用者不能处理异常,如果调用者调用一个方法,你可以让你的类“什么也不做”——如果传递给构造函数的文件不是目录,基本上忽略所有“做”的请求
这是一个调用,并且有一些库,用于帮助您检查传入参数。对我来说,第三个更可取。较短,您可以将其用于多个文件。另外,您不需要从isValid方法返回新对象,并且仅当isValid从代码的另一部分返回true时,才创建该对象。这很好,因为它分离了功能。@AliAlamiri在干净的代码方面是我的首选,因为阅读它最有意义,也就是说,读者清楚正在发生什么。我只是不能决定我是否喜欢调用方必须知道他应该首先调用isValid这一事实。为什么不将该文件传递给构造函数并在那里检查该文件是否有效呢。这会在构造对象和h时对调用方隐藏检查