Java I/O通用api设计 (一)

原文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个部分。

  1. 客户代码,初始化了传输,要知道输入和输出的源。
  2. 从输入中读的代码。
  3. 辅助代码,用于跟踪整个过程。这些代码我希望能够重用,而不管是何种传输的类型。
  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 {
    }
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值