Cactoos 中面向对象的声明式输入/输出

位置:首页>文章>详情   分类:Java教程   阅读(104)   2023-12-09 07:14:57

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 的美妙之处了吗?

标签: Java教程
地址:https://www.cundage.com/article/jcg-object-oriented-declarative-inputoutput-cactoos.html