我已经读了几篇关于不变性的文章,但仍然没有很好地理解这个概念。
我最近在这里做了一个提到不变性的帖子,但由于这本身就是一个主题,我现在正在制作一个专门的帖子。
我在过去的帖子中提到过,我认为不变性是将对象设为只读并使其具有低可见性的过程。另一位成员表示,这与此没有任何关系。This page (series的一部分)使用了一个不可变的类/结构的例子,它使用只读和其他概念来锁定它。
在本例中,状态的定义到底是什么?状态是一个我还没有真正掌握的概念。
从设计指南的角度来看,不可变的类必须是不接受用户输入并且真正只返回值的类。
我的理解是,任何只返回信息的对象都应该是不可变的和“锁定的”,对吧?因此,如果我想用那个方法在专用类中返回当前时间,我应该使用引用类型,因为这将是该类型的引用,因此我受益于不变性。
发布于 2009-03-08 00:00:36
什么是不变性?
动物类的不可变性主要应用于对象(字符串、数组、自定义的动物class)
这与用户输入没有任何直接关系;即使是您的代码也不能更改不可变对象的值。但是,您始终可以创建一个新的不可变对象来替换它。这里是一个伪代码示例;请注意,在许多语言中,您可以简单地使用myString = "hello";,而不是像我下面那样使用构造函数,但为了清楚起见,我将其包含在内:
String myString = new ImmutableString("hello");
myString.appendString(" world"); // Can't do this
myString.setValue("hello world"); // Can't do this
myString = new ImmutableString("hello world"); // OK你提到“一个只返回信息的对象”;这并不会自动使它成为不变性的一个很好的候选者。不可变对象倾向于总是返回与它们构造时相同的值,所以我倾向于说当前时间不是理想的,因为它经常改变。但是,您可以使用特定的时间戳创建MomentOfTime类,并在将来始终返回该时间戳。
不变性的好处
String myString = "HeLLo WoRLd";String lowercasedString =小写( myString );print myString+“转换为”+ lowercasedString;
如果lowercase()的实现在创建小写版本时更改了myString,该怎么办?第三行不会给你想要的结果。当然,一个好的lowercase()函数不会做到这一点,但是如果myString是不可变的,这一点是可以肯定的。因此,不可变对象可以帮助实施良好的面向对象编程更容易创建不可变对象thread-safe
状态
如果你把一个对象的所有实例变量写在纸上,那就是该对象在给定时刻的状态。程序的状态是它的所有对象在给定时刻的状态。随着时间的推移,状态会迅速变化;程序需要改变状态才能继续运行。
然而,随着时间的推移,不可变对象具有固定的状态。一旦创建,不可变对象的状态就不会改变,尽管整个程序的状态可能会改变。这使得跟踪正在发生的事情变得更容易(并看到上面的其他好处)。
发布于 2009-03-08 00:11:49
不变性
简单地说,当内存在初始化后没有被修改时,它是不可变的。
用命令式语言(如C、Java和C# )编写的程序可以随意操作内存中的数据。物理内存的一个区域,一旦留在一边,就可以在程序执行期间的任何时间被执行线程全部或部分修改。事实上,命令式语言鼓励这种编程方式。
以这种方式编写程序对于单线程应用程序来说是非常成功的。然而,随着现代应用程序开发转向单个进程中的多个并发操作线程,引入了一系列潜在的问题和复杂性。
当只有一个执行线程时,您可以想象这个线程“拥有”内存中的所有数据,因此可以随意操作它。但是,当涉及多个执行线程时,没有隐含的所有权概念。
相反,这一负担落在程序员身上,他们必须竭尽全力确保内存中的结构对于所有读取器都处于一致的状态。必须谨慎地使用锁定构造,以防止一个线程在数据被另一个线程更新时看到数据。如果没有这种协调,线程将不可避免地消耗仅更新到一半的数据。这种情况的结果是不可预测的,而且往往是灾难性的。此外,让锁定在代码中正确工作是出了名的困难,如果做得不好,可能会削弱性能,或者在最坏的情况下,死锁将无法恢复地停止执行。
使用不可变的数据结构可以减少在代码中引入复杂锁定的需要。当保证一段内存在程序的生命周期内不会改变时,多个读取器可以同时访问该内存。他们不可能观察到处于不一致状态的特定数据。
许多函数式编程语言,如Lisp、Haskell、Erlang、F#和Clojure,本质上都支持不可变的数据结构。正是由于这个原因,随着我们朝着日益复杂的多线程应用程序开发和多计算机计算机体系结构发展,它们重新引起了人们的兴趣。
状态
应用程序的状态可以简单地看作是给定时间点的所有内存和CPU寄存器的内容。
从逻辑上讲,程序的状态可以分为两种:
在托管环境中,例如C#和Java,一个线程不能访问另一个线程的内存。因此,每个线程都“拥有”其堆栈的状态。堆栈可以看作是保存值类型(struct)的局部变量和参数,以及对对象的引用。这些值与外部线程隔离。
但是,堆上的数据可以在所有线程之间共享,因此必须小心控制并发访问。所有引用类型(class)对象实例都存储在堆中。
在OOP中,类实例的状态是由它的字段决定的。这些字段存储在堆中,因此所有线程都可以访问它们。如果一个类定义了允许在构造函数完成后修改字段的方法,那么这个类是可变的(不是不可变的)。如果字段不能以任何方式更改,则类型是不可变的。需要注意的是,具有所有C# readonly/Java final字段的类不一定是不可变的。这些构造确保引用不能更改,但不能更改被引用的对象。例如,字段可能具有对对象列表的不可更改引用,但列表的实际内容可以随时修改。
通过将类型定义为真正不可变的,它的状态可以被认为是冻结的,因此该类型对于多线程访问是安全的。
在实践中,将所有类型定义为不可变可能会很不方便。要修改不可变类型上的a值,可能需要进行大量的内存复制。一些语言使这个过程比其他语言更容易,但无论哪种方式,CPU最终都会做一些额外的工作。许多因素决定了复制内存所花费的时间是否超过了锁定争用的影响。
很多研究已经进入了不可变数据结构的开发中,例如列表和树。当使用这样的结构时,比如一个列表,'add‘操作将返回一个新列表的引用,其中添加了新的项。对上一个列表的引用看不到任何更改,并且仍然具有一致的数据视图。
发布于 2009-03-07 23:02:57
简单地说:一旦创建了一个不可变的对象,就无法更改该对象的内容。.Net不可变对象的示例包括字符串和Uri。
当你修改一个字符串时,你只是得到了一个新的字符串。原始字符串不会更改。Uri只有只读属性,没有可用于更改Uri内容的方法。
不可变对象很重要的情况多种多样,而且在大多数情况下都与安全性有关。Uri就是一个很好的例子。(例如,您不希望Uri被某些不受信任的代码更改。)这意味着您可以传递对不可变对象的引用,而不必担心内容会发生变化。
希望这能有所帮助。
https://stackoverflow.com/questions/622664
复制相似问题