原文A generic input/output API in Java(by Rickard Öberg)中给出了一个通用Java IO API设计,并且有API的Demo代码。更重要的一点是,这篇文章给出实现这个API设计本身的步骤和过程,这让API设计实现过程有了条理。文中示范了从 普通简单实现 整理成 正确分解、可以复用、可扩展的API设计 的过程。这个很值得理解和学习! PS: 设计偏向是艺术,一个赏心悦目的设计,尤其是API设计,旁人看来多是妙手偶得的感觉,如果有些章可循真是一件好事。给出 减少艺术的艺术工作量的方法的人是大师。
大家应该都写过java的IO代码,我觉得那些代码都丑的不行,充斥了很多业务无关的代码,很多的try catch。但是想要抽象的时候往往又无从下手。让我们看一个读文件然后写入另一个文件的典型场景,看看能不能从中发现包含了哪几个部分。
1: File source = new File( getClass().getResource( "/iotest.txt" ).getFile() );
1: File destination = File.createTempFile( "test", ".txt" );
1: destination.deleteOnExit();
2: BufferedReader reader = new BufferedReader(new FileReader(source));
3: long count = 0;
2: try
2: {
4: BufferedWriter writer = new BufferedWriter(new FileWriter(destination));
4: try
4: {
2: String line = null;
2: while ((line = reader.readLine()) != null)
2: {
3: count++;
4: writer.append( line ).append( '\n' );
2: }
4: writer.close();
4: } catch (IOException e)
4: {
4: writer.close();
4: destination.delete();
4: }
2: } finally
2: {
2: reader.close();
2: }
1: System.out.println(count)
行左边的数字是我标识的4个部分。
- 客户代码,初始化了传输,要知道输入和输出的源。
- 从输入中读的代码。
- 辅助代码,用于跟踪整个过程。这些代码我希望能够重用,而不管是何种传输的类型。
- 最后这个部分是接收数据,写数据。这个代码,我要批量读写,可以在第2第4部分修改,改成一次处理多行。
API
一旦明确上面划分的内容,剩下就只是为每个部分整理成一个接口,并保证在各种场景能方便使用。结果如下。 首先要有输入,即Input接口:
public interface Input<T, SenderThrowableType extends Throwable>
{
<ReceiverThrowableType extends Throwable> void transferTo( Output<T,ReceiverThrowableType> output )
throws SenderThrowableType, ReceiverThrowableType;
}
Input,如Iterables,可以被多次使用,用于初始化一处到另一处的传输。因为我泛化传输的数据类型为T,所以可以是任何类型(byte[]、String、EntityState、MyDomainObject)。为了让发送者和接收者可以抛出各自的异常,接口上把各自己的异常声明成了类型参数。比如:在出错的时,Input抛的可以是SQLException,Output抛的是IOException。异常是强类型的,并且在出错时发送和接收双方都必须知道的,这使的双方做合适的恢复操作,关闭他们打开了的资源。
在接收端的是Output接口:
public interface Output<T, ReceiverThrowableType extends Throwable>
{
<SenderThrowableType extends Throwable> void receiveFrom(Sender<T, SenderThrowableType> sender)
throws ReceiverThrowableType, SenderThrowableType;
}
当receiveFrom方法被Input调用时(通过调用Input的transferTo方法触发),Output应该打开好了它所需要的资源,然后期望数据从Sender发送过来。Input和Output必须要有类型T,两者对要发送的内容达到一致。后面我们可以看到如何处理不一致的情况。
接下来是Sender接口:
public interface Sender<T, SenderThrowableType extends Throwable>
{
<ReceiverThrowableType extends Throwable> void sendTo(Receiver<T, ReceiverThrowableType> receiver)
throws ReceiverThrowableType, SenderThrowableType;
}
Output调用sendTo方法,传入一个Receiver,Sender使用这个Receiver来发送一个一个的数据。Sender在这个时候发起传输,把类型数据T传输到Receiver,一次一个。Receiver接口如下:
public interface Receiver<T, ReceiverThrowableType extends Throwable>
{
void receive(T item)
throws ReceiverThrowableType;
}
当Receiver从Sender收到数据时,即可以马上写到底层资源中,也可以分批写入。Receiver知道传输什么时候结束(sendTo方法返回了),所以正确写入剩下的分批数据、关闭持有的资源。
这个简单的模式在发送方和接收方各有2个接口,并保持了以可伸缩、高性能和容错的方式传输数据的潜能。
标准化I/O
上文的API定义了数据发送和接收的契约,然后可以制定几个输入输出(I/O)的标准。比如:从文本文件中读取文本行后再写成文本文件。这个操作可以静态方法中,方便的重用。最后,拷贝文本文件可以写成:
File source = ...
File destination = ...
Inputs.text( source ).transferTo( Outputs.text(destination) );
一行代码处理了读文件、写文件、资源清理和其它零零碎碎的操作。真心的赞!transferTo方法会抛出IOException,要向用户显示Error可以catch这个异常。但实际处理这些Error往往是,关闭文件,把没有写成功的文件删除,而这些Input、Output已经处理好了。我们再也不需要关心文件读写的细节!
下面是实现的代码
Input的实现:
public class FileInput implements Input<String,IOException> {
final File source;
final Reader reader;
public FileInput(File source) throws IOException {
this.source = source;
reader = new FileReader(source);
}
public <ReceiverThrowableType extends Throwable> void transferTo(Output<String, ReceiverThrowableType> output)
throws IOException, ReceiverThrowableType {
final StringSender sender = new StringSender(reader);
output.receiveFrom(sender);
try {
reader.close();
} catch (Exception e) {
// ignore close exception :)
}
}
}
Output的实现:
public class FileOutput implements Output<String, IOException> {
final File destination;
final Writer writer;
public FileOutput(File destination) throws IOException {
this.destination = destination;
writer = new FileWriter(destination);
}
public <SenderThrowableType extends Throwable> void receiveFrom(Sender<String, SenderThrowableType> sender)
throws IOException, SenderThrowableType {
final StringReceiver receiver = new StringReceiver(writer);
sender.sendTo(receiver);
receiver.finished();
try {
writer.close();
} catch (Exception e) {
// ignore close exception
}
}
}
Sender的实现:
public class StringSender implements Sender<String,IOException> {
final Reader reader;
BufferedReader bufferReader;
public StringSender(Reader reader) throws FileNotFoundException {
this.reader = reader;
this.bufferReader = new BufferedReader(reader);
}
@Override
public <ReceiverThrowableType extends Throwable> void sendTo(Receiver<String, ReceiverThrowableType> receiver)
throws ReceiverThrowableType, IOException {
String readLine;
while((readLine = bufferReader.readLine()) != null) {
receiver.receive(readLine + "\n");
}
}
}
Receiver的实现:
public class StringReceiver implements Receiver<String, IOException> {
final Writer writer;
public StringReceiver(Writer writer) throws IOException {
this.writer = writer;
}
@Override
public void receive(String item) throws IOException {
writer.write(item);
}
public void finished() throws IOException {
}
}