为什么我们需要不可变类?

2022-08-31 10:53:29

我无法理解我们需要一个不可变类的场景是什么。
你有没有遇到过这样的要求?或者你能不能给我们任何真实的例子,我们应该使用这个模式。


答案 1

其他答案似乎过于专注于解释为什么不变性是好的。它非常好,我尽可能地使用它。但是,这不是你的问题。我会逐点回答你的问题,试图确保你得到你需要的答案和例子。

我无法理解我们需要不可变类的场景。

“需要”在这里是一个相对的术语。不可变类是一种设计模式,与任何范式/模式/工具一样,它使构建软件更容易。同样,在OO范式出现之前已经编写了大量的代码,但是将我算作“需要”OO的程序员之一。不可变类,如OO,不是严格需要的,但我会表现得好像我需要它们一样。

你有没有遇到过这样的要求?

如果您没有以正确的视角查看问题域中的对象,则可能看不到对不可变对象的要求。如果您不熟悉何时有利地使用它们,则可能很容易认为问题域不需要任何不可变的类。

我经常使用不可变类,其中我将问题域中的给定对象视为值或固定实例。这个概念有时取决于视角或观点,但理想情况下,很容易切换到正确的视角来识别好的候选对象。

通过确保你阅读各种书籍/在线文章,可以更好地了解不可变对象真正有用的地方(如果不是严格必要的),以培养如何思考不可变类的良好意识。一篇让你入门的好文章是Java理论和实践:突变还是不突变?

我将尝试在下面给出几个例子,说明如何以不同的视角(可变与不可变)查看对象,以阐明我所说的视角是什么意思。

...你能不能给我们任何真实的例子,我们应该使用这个模式。

既然你要求真实的例子,我会给你一些,但首先,让我们从一些经典的例子开始。

经典值对象

字符串和整数通常被视为值。因此,发现 String 类和 Integer 包装器类(以及其他包装器类)在 Java 中是不可变的也就不足为奇了。颜色通常被认为是一个值,因此是不可变的 Color 类。

反例

相比之下,汽车通常不被认为是一个价值对象。对汽车进行建模通常意味着创建一个具有不断变化的状态(里程表,速度,燃油油位等)的类。但是,在某些域中,它可能是一个值对象。例如,汽车(或特别是汽车模型)可能被视为应用中的值对象,以查找给定车辆的正确机油。

打牌

曾经写过扑克牌程序吗?我做了。我本可以将扑克牌表示为具有可变套装和等级的可变对象。一手平局扑克牌可以是5个固定的实例,其中替换我手中的第5张牌意味着通过改变其花色和等级ivars将第5张扑克牌实例变异为新牌。

但是,我倾向于将扑克牌视为一个不可变的对象,一旦创建,它就具有固定不变的花色和等级。我的抽奖扑克牌将是5个实例,替换我手中的一张牌将涉及丢弃其中一个实例并向我的手牌添加一个新的随机实例。

地图投影

最后一个例子是,当我处理一些地图代码时,地图可以在各种投影中显示自己。原始代码让地图使用固定但可变异的投影实例(如上面的可变扑克牌)。更改地图投影意味着更改地图投影实例的 ivar(投影类型、中心点、缩放等)。

但是,如果将投影视为不可变值或固定实例,则感觉设计更简单。更改地图投影意味着让地图引用不同的投影实例,而不是改变地图的固定投影实例。这也使得捕获命名投影(如 .MERCATOR_WORLD_VIEW


答案 2

不可变类通常更易于正确设计、实现和使用。一个例子是 String:的实现比 C++ 中的实现简单得多,这主要是由于它的不可变性。java.lang.Stringstd::string

不可变性产生特别大差异的一个特定领域是并发性:不可变对象可以在多个线程之间安全地共享,而可变对象必须通过仔细的设计和实现使线程安全 - 通常这远非一项微不足道的任务。

更新:有效的 Java 第 2 版详细解决了此问题 - 请参见第 15 项:最小化可变性

另请参阅以下相关文章:


推荐