我最近已经阅读了很多关于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");
}
};
}
如果我的构造函数抛出异常,那么它实际上是一种特殊情况,因为期望它只是传递有效目录。这样做意味着我可以从构造函数/工厂中删除对已检查异常的需要,因为任何异常都会在某处显示错误而不是预期的行为。但它仍然存在是否将其置于构造函数或工厂方法中的问题。
答案 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)构造函数。如果有一个合理的期望,调用者只会传入目录,那么请继续使用。
如果有合理的期望调用者可以传入非目录,那么您有两种选择:
答案 4 :(得分:0)
这称为Design-by-Contract,并且有一些库可以帮助您检查传入的参数。