class CSVReader {
private List<String> output;
private InputStream input;
public CSVReader(InputStream input) {
this.input = input;
}
public void read() throws Exception{
//do something with the inputstream
// create output list.
}
public List<String> getOutput() {
return Collections.unmodifiableList(output);
}
我正在尝试创建一个简单的类,它将成为库的一部分。我想创建满足以下条件的代码:
现在,当我评估上面的代码时,针对目标,我意识到我失败了。使用此代码的开发人员必须编写类似这样的内容 -
CSVReader reader = new CVSReader(new FileInputStream("test.csv");
reader.read();
read.getOutput();
我马上看到以下问题 - - 开发人员必须在getOutput之前先调用read。他没有办法直观地了解这一点,这可能是糟糕的设计。
所以,我决定修改代码并编写类似这样的内容
public List<String> getOutput() throws IOException{
if(output==null)
read();
return Collections.unmodifiableList(output);
}
或者这个
public List<String> getOutput() {
if(output==null)
throw new IncompleteStateException("invoke read before getoutput()");
return Collections.unmodifiableList(output);
}
或者这个
public CSVReader(InputStream input) {
read(); //throw runtime exception
}
或者这个
public List<String> read() throws IOException {
//read and create output list.
// return list
}
实现目标的好方法是什么?对象状态是否应始终明确定义? - 从来没有一个未定义“输出”的状态,所以我应该将输出创建为构造函数的一部分?或者类应该确保创建的实例始终有效,只要它发现未定义“output”并且只是抛出运行时异常,就调用“read”?这里有什么好方法/最佳实践?
答案 0 :(得分:2)
我会将read()
设为私有,并getOutput()
将其称为实施细节。如果公开read()
的目的是延迟加载文件,则只需公开getOutput
public List<String> getOutput() {
if (output == null) {
try {
output = read();
} catch (IOException) {
//here you either wrap into your own exception and then declare it in the signature of getOutput, or just not catch it and make getOutput `throws IOException`
}
}
return Collections.unmodifiableList(output);
}
这样做的好处是你的类的接口非常简单:你给我一个输入(通过构造函数)我给你一个输出(通过getOutput),没有魔术顺序的调用,同时保留延迟加载这是很好的如果文件很大。
从公共API中删除read
的另一个好处是,您可以从延迟加载到急切加载,而不会影响您的客户端。如果您公开read
,则必须考虑在对象的所有可能状态下调用它(在它加载之前,在它已经加载之后,在它已经加载之后)。简而言之,总是暴露尽可能少的
所以要解决您的具体问题:
read
上进行外部调用的观点确实是一种设计气味read
并急切地加载所有内容。决定是否延迟加载是一个依赖于你的上下文的实现细节,它对你班级的客户来说无关紧要read
则抛出异常会再次成为在客户端上以正确的隐式顺序调用事物的负担,这是不必要的,因为您的注释输出从未真正未定义,因此实现本身可以做出何时调用read
答案 1 :(得分:1)
我建议你让你的课程尽可能小,将Unique_id Rating_Code Rating
123 11 1
123 21 3
123 31 (null)
456 11 -1
456 21 (null)
456 31 2
方法放在一起。
这个想法是让一个类读取CSV文件并返回一个表示结果的列表。为此,您可以公开一个getOutput()
方法,该方法将返回read()
。
类似的东西:
List<String>
你有一个定义良好的类,一个需要维护的小接口,public class CSVReader {
private final InputStream input;
public CSVReader(String filename) {
this.input = new FileInputStream(filename);
}
public List<String> read() {
// perform the actual reading here
}
}
的实例是不可变的。
答案 2 :(得分:1)
read()
,然后多次调用getOutput()
无异常。他很高兴。您的更改会强制用户在以前不必要的情况下捕获已检查的异常。read()
是调用getOutput()
的先决条件,所以你的班级有责任去捕捉&#34; 34;你的用户忘记&#34;打电话给read()
。IOException
,这可能是捕获的合法例外。没有办法让用户知道是否会抛出异常,这在设计运行时异常时是一种不好的做法。问题的根本原因是该类有两个正交职责:
如果你将这两个责任相互分开,你最终会得到一个更清洁的设计,在这个设计中,用户不会对他们必须调用的内容以及按什么顺序混淆:
interface CSVData {
List<String> getOutput();
}
class CSVReader {
public static CSVData read(InputStream input) throws IOException {
...
}
}
您可以使用工厂方法将两者合并为一个类:
class CSVData {
private CSVData() { // No user instantiation
}
// Getting data is exception-free
public List<String> getOutput() {
...
}
// Creating instances requires a factory call
public static CSVData read(InputStream input) throws IOException {
...
}
}
答案 3 :(得分:1)
让getOutput
检查它是否为空(或过期)并自动加载(如果是)。这允许您的类的用户不必关心类的文件管理的内部状态。
但是,您可能还希望公开read
函数,以便用户可以在方便时选择加载文件。如果您为并发环境创建类,我建议您这样做。