Gradients of Immutability

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

好的对象是不可变的,但不一定是常量。我在这里、这里和这里试图解释它,但现在是时候再试一次了。实际上,我越想越明白,不可变性并不是非黑即白的—还有一些更多的渐变;让我们来看一下。

正如我们在这里达成的共识,一个对象是某个实体(其他对象、数据、内存、文件等)的代表。让我们来分析一些在我们看来完全相同但代表不同事物的对象,然后分析它们的不可变性以及原因。

这是常量;它不允许对封装实体进行任何修改,并且始终返回相同的文本(出于简洁起见,我省略了构造函数)。

这是我们通常在谈论不可变对象时所想到的。这样的类非常接近于纯函数,这意味着无论我们用相同的初始值实例化多少次,title()的结果都会是相同的。

Check out this one:

class Book {
  private final String ttl;
  Book rename(String title) {
    return new Book(title);
  }
  String title() {
    return String.format(
      "%s (as of %tR)", this.ttl, new Date()
    );
  }
}

这个对象仍然是不可变的,但由于title()方法的存在,它不再是一个纯函数——如果我们以至少一分钟的间隔多次调用它,它会返回不同的值。这个对象是不可变的,只是不再是一个常量了。

How about this one:

class Book {
  private final Path path;
  Book rename(String title) {
    Files.write(
      this.path,
      title.getBytes(),
      StandardOpenOption.CREATE
    );
    return this;
  }
  String title() {
    return new String(
      Files.readAllBytes(this.path)
    );
  }
}

这个不可变的对象将书的标题保存在一个文件中。它不是一个常量,因为它的方法title()在每次调用时可能返回不同的值。此外,表示的实体(文件)也不是一个常量。我们无法确定Files.write()的实现方式,因此无法确定它是可变的还是不可变的。但我们可以确定它不是一个常量,因为它接受更改请求。

一个不可变对象不仅可以表示,甚至可以封装一个可变对象。就像在之前的例子中,一个可变的文件被封装起来。即使它被不可变类Path表示,磁盘上的实际文件是可变的。我们可以在内存中做同样的事情:

这个对象仍然是不可变的。它是线程安全的吗?不是。它是常量吗?不是。它是不可变的吗?是的。感到困惑了吗?当然。

我的观点是,不可变性不是“二元”的;它有很多形式。最简单的一种形式当然是常量。常量几乎与函数式编程中的纯函数相同。但是面向对象编程使我们能够更进一步,给不可变对象提供更多的权限和灵活性。在面向对象编程中,我们可能会有更多形式的不可变性。

所有这些示例中共同的特点是,我们的对象对其封装的实体是忠诚的。没有可能改变它们的设置器。所有封装的对象都是final的。

这是将可变对象与不可变对象区分开来的唯一特性。后者始终对其封装和代表的实体忠诚。至于其他方面… 这取决于情况。

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-11-17 at 17:09

sixnines availability badge   GitHub stars