Cactoos 是一个面向对象的 Java 原语库,我们 几周前才开始着手开发。目的是为 JDK、Guava、Apache Commons 等提出一个干净且更具声明性的替代方案。我们不想调用静态过程,而是希望使用对象,按照它们应该使用的方式。让我们看看输入/输出如何以纯面向对象的方式工作。
假设你想读取一个文件。这就是使用 utility class readAllBytes()
Files
的方法span> 在 JDK7 中:
byte[] content = Files.readAllBytes( new File("/tmp/photo.jpg").toPath() );
这段代码非常必要——它立即读取文件内容,并将其放入数组中。
这是使用 Cactoos 的方式:
Bytes source = new InputAsBytes( new FileAsInput( new File("/tmp/photo.jpg") ) );
注意——还没有方法调用。只有三个构造函数或三个类组成了一个更大的对象。对象 source
的类型为 Bytes
并表示文件的内容。为了从中获取内容,我们调用它的方法 asBytes()
:
bytes[] content = source.asBytes();
这是接触文件系统的时刻。如您所见,这种方法是绝对声明式的,因此具有面向对象的所有优点。
这是另一个例子。假设您想将一些文本写入文件。以下是您在 Cactoos 中的操作方法。首先你需要 Input
:
Input input = new BytesAsInput( new TextAsBytes( new StringAsText( "Hello, world!" ) ) );
然后你需要 Output
:
Output output = new FileAsOutput( new File("/tmp/hello.txt") );
现在,我们要将输入复制到输出。 纯 OOP 中没有“复制”操作。而且,绝对不能有任何操作。只是对象。我们有一个名为 TeeInput
的类,它是一个 Input
,它将您从中读取的所有内容复制到 Output
,类似于 Apache Commons 中的 TeeInputStream
所做的,但被封装了。所以我们不复制,我们创建一个 Input
如果你触摸它就会复制:
Input tee = new TeeInput(input, output);
现在,我们必须“触摸”它。我们必须触摸它的每个字节,以确保它们都被复制。如果我们只是read()
第一个字节,那么只有一个字节会被复制到文件中。接触它们的最佳方法是逐字节计算 tee
对象的大小。我们有一个对象,叫做 LengthOfInput
。它封装了一个 Input
并且表现得像它的字节长度:
Scalar<Long> length = new LengthOfInput(tee);
然后我们从中取出值并进行文件写入操作:
long len = length.asValue();
因此,将字符串写入文件的整个操作如下所示:
new LengthOfInput( new TeeInput( new BytesAsInput( new TextAsBytes( new StringAsText( "Hello, world!" ) ) ), new FileAsOutput( new File("/tmp/hello.txt") ) ) ).asValue(); // happens here
这是它在 JDK7 中的替代程序:
Files.write( new File("/tmp/hello.txt").toPath(), "Hello, world!".getBytes() );
“为什么面向对象更好,即使它更长?”我听到你问。因为它完美地解耦概念,而过程性概念则将它们放在一起。
比方说,您正在设计一个类,该类应该加密一些文本并将其保存到文件中。以下是您将如何以程序方式设计它(当然不是真正的加密):
class Encoder { private final File target; Encoder(final File file) { this.target = file; } void encode(String text) { Files.write( this.target, text.replaceAll("[a-z]", "*") ); } }
工作正常,但是当您决定扩展它以写入 OutputStream
时会发生什么?你将如何修改这个类?之后会难看到什么程度呢?那是因为设计不是面向对象的。
这就是使用 Cactoos 以面向对象的方式进行相同设计的方法:
class Encoder { private final Output target; Encoder(final File file) { this(new FileAsOutput(file)); } Encoder(final Output output) { this.target = output; } void encode(String text) { new LengthOfInput( new TeeInput( new BytesAsInput( new TextAsBytes( new StringAsText( text.replaceAll("[a-z]", "*") ) ) ), this.target ) ).asValue(); } }
如果我们希望 OutputStream
被接受,我们如何处理这个设计?我们只添加一个 secondary 构造函数:
class Encoder { Encoder(final OutputStream stream) { this(new OutputStreamAsOutput(stream)); } }
完毕。这就是它的简单和优雅。
这是因为概念是完全分离的,功能是封装的。在程序示例中,对象的行为位于它之外,在方法 encode()
中。文件本身不知道如何写,一些外部程序 Files.write()
知道。
相反,在面向对象的设计中,FileAsOutput
知道如何编写,而没有其他人知道。文件写入功能被封装,这使得以任何可能的方式装饰对象成为可能,创建可重用和可替换的复合对象。
您现在看到 OOP 的美妙之处了吗?
标签2: Java教程地址:https://www.cundage.com/article/jcg-object-oriented-declarative-inputoutput-cactoos.html