Smalltalk 如何创建不可变的实例变量



我有一个带有实例变量var的类。
我不希望将变量修改/分配给值,除非使用 Class 方法创建对象。

是不可变的:aBoolean 是将可变对象转换为不可变对象的方法,反之亦然。
有人可以给我提供正确的语法吗?

我还没有尝试过,但我很确定 isImmutable 不会解决问题。 假设这确实使对象不可变,它将使 instVar 指向的对象不可变,而不是 instvar 本身。

最好的办法是根本不包含变量的特定 Mutator,而是在初始化时设置它,如下所示:

MyClass class>>newWithFoo: aFoo
   ^self basicNew initializeWithFoo: aFoo; yourself
MyClass>>initializeWithFoo: aFoo
   self initialize.
   foo := aFoo.

这样,类本身之外的任何人都可以影响变量的唯一方法是通过调用MyClass newWithFoo:创建一个新实例

(不包括使用像#instVarNamed:put:这样的反射方法 - 你几乎无能为力,但任何使用它们的人都知道他们无论如何都会破坏类的契约)。

为什么要使对象不可变?以一种显而易见的方式声明您的 API 是否足以清楚地了解如何使用类而不替换实例变量?

我使用以下代码实现了它:

MyClass class>>classMethod: aValue
anObject := self new value:aValue.
anObject isImmutable: true.
^anObject.

要记住的一件事是,不可变性在对象图中的单个级别运行。实例变量有点像C++中的常量指针,地址不能改变,但内容可以改变。
这里有一个小例子:

| a b |
a := Array with: 1.
b := Array with: a.
b beImmutable.
b at: 1 put: nil.
^b

最终会得到一个NoModificationError,你不能写b的任何实例/索引变量,因为b是不可变的。
但是你可以写入 b 的实例/索引变量所指向的对象:

| a b |
a := Array with: 1.
b := Array with: a.
b beImmutable.
a at: 1 put: 2.
^b

将成功,b 现在是

#(#(2)) 而不是 #(#(1))

你也可以安排在对象图上进一步传播不变性,如果这是你所追求的(但要注意循环)。

最新更新