为其他开发人员设计在java中使用的类

时间:2016-02-02 20:30:48

标签: java

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”?这里有什么好方法/最佳实践?

4 个答案:

答案 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,则必须考虑在对象的所有可能状态下调用它(在它加载之前,在它已经加载之后,在它已经加载之后)。简而言之,总是暴露尽可能少的

所以要解决您的具体问题:

  1. 是的,对象状态应始终明确定义。你不知道客户类需要在read上进行外部调用的观点确实是一种设计气味
  2. 是的,你可以在构造函数中调用read并急切地加载所有内容。决定是否延迟加载是一个依赖于你的上下文的实现细节,它对你班级的客户来说无关紧要
  3. 如果未调用read则抛出异常会再次成为在客户端上以正确的隐式顺序调用事物的负担,这是不必要的,因为您的注释输出从未真正未定义,因此实现本身可以做出何时调用read
  4. 的无风险决定

答案 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)

  1. 第一种方法从API中消除了一些灵活性:在更改之前,用户可以在预期出现异常的上下文中调用read(),然后多次调用getOutput()无异常。他很高兴。您的更改会强制用户在以前不必要的情况下捕获已检查的异常。
  2. 第二种方法是首先应该如何完成:因为调用read()是调用getOutput()的先决条件,所以你的班级有责任去捕捉&#34; 34;你的用户忘记&#34;打电话给read()
  3. 第三种方法隐藏IOException,这可能是捕获的合法例外。没有办法让用户知道是否会抛出异常,这在设计运行时异常时是一种不好的做法。
  4. 问题的根本原因是该类有两个正交职责:

    • 阅读CSV,
    • 存储读取结果供以后使用。

    如果你将这两个责任相互分开,你最终会得到一个更清洁的设计,在这个设计中,用户不会对他们必须调用的内容以及按什么顺序混淆:

    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函数,以便用户可以在方便时选择加载文件。如果您为并发环境创建类,我建议您这样做。