2009-08-16 97 views
9

我曾在一些设计书中读过不可变类提高可伸缩性,并尽可能编写不可变类的良好实践。但我认为如此不可改变的类增加对象的扩散。那么,为了提高可伸缩性,去不可变类还是更好去静态类(静态所有方法的类)呢?可变或不可变类?

回答

5

不可变类做宣传对象的增殖,但如果你想安全,可变对象将推动更多对象扩散,因为你必须返回副本而不是原始的,以防止用户更改您返回的对象。

至于使用类的所有静态方法,这不是真正在其中可以使用不变性多数情况下的选择。从RPG中获取这个例子:

public class Weapon 
{ 
    final private int attackBonus; 
    final private int accuracyBonus; 
    final private int range; 

    public Weapon(int attackBonus, int accuracyBonus, int range) 
    { 
     this.attackBonus = attackBonus; 
     this.accuracyBonus = accuracyBonus; 
     this.range = range; 
    } 

    public int getAttackBonus() { return this.attackBonus; } 
    public int getAccuracyBonus() { return this.accuracyBonus; } 
    public int getRange() { return this.range; } 
} 

你究竟如何用一个只包含静态方法的类实现呢?

12

然而,immutable类的主要好处是您可以公开内部数据成员,这些成员是不可变的,因为调用者无法修改它们。这是一个很大的问题,例如java.util.Date。它是可变的,所以你不能直接从方法返回它。这意味着你最终会做各种各样的defensive copying。这增加了对象扩散。

另一个主要好处是根据定义,不可变对象没有synchronization问题。这就是scalability问题。编写multithreaded代码很难。不可变的对象是(主要)规避问题的好方法。

至于“静态类”,通过您的评论,我认为它是指类factory methods,这是它通常如何描述。这是一个不相关的模式。可变类和不可变类都可以具有公共构造函数或具有静态工厂方法的私有构造函数。这对类的(im)可变性没有影响,因为可变类是可以在创建后更改其状态的类,而不可变类的状态在实例化后不能更改。

但是静态工厂方法可以有其他好处。这个想法是封装对象创建。

+1

静态类是一个你永远不会实例化的类,但是使用它提供的方法(例如java中的Arrays)。我看不出他们如何取代不可变的类... – Zed 2009-08-16 17:00:02

+0

这里的“静态类”可能只是“不可变类”的同义词。没有别的道理。 – CPerkins 2009-08-16 17:02:47

+0

使用“静态类”我的意思是说一个类,其中包含所有具有私有构造函数的静态方法(因此,用户不能创建同一类的实例),没有任何静态成员。对不起,造成混乱。 – 2009-08-16 17:07:50

1

作为cletus表示,不可变类简化类设计和处理同步方法。

它们还简化了集合中的处理,即使在单线程应用程序中也是如此。一个不可变的类永远不会改变,所以key和hashcode不会改变,所以你不会搞砸你的集合。

但是你应该记住你正在建模的东西的生命周期和构造函数的“重量”。如果你需要改变这个事物,不可变对象变得更加复杂。你必须替换它们,而不是修改它们。不可怕,但值得考虑。如果构造函数花费的时间不长,那也是一个因素。

0

不变性通常用于实现可伸缩性,因为不变性是Java中并发编程的推动因素之一。所以,正如你所指出的,在“不可变”的解决方案中可能有更多的对象,这可能是提高并发性的必要步骤。

另一个同样重要的用途是不可消化的设计意图;谁创造了一个不可改变的阶级,意味着你在其他地方放置了可变状态。如果你开始改变这个类的实例,那么你可能违背了设计的初衷 - 谁知道后果可能是什么。

0

考虑字符串对象,作为示例。一些语言或类库提供可变字符串,有些则不。

使用不可变字符串的系统可以执行某些优化,即带有可变字符串的系统不能。例如,您可以确保只有任何唯一字符串的一个副本。由于对象“开销”的大小通常远小于任何非平凡字符串的大小,因此这可能会节省大量内存。还有其他潜在的空间节省,如实习子网。

除了潜在的内存节省之外,不可变对象还可以通过减少争用来提高可伸缩性。如果您有大量线程访问相同的数据,那么不可变对象不需要精心设计的同步过程来进行安全访问。

0

只是关于这个问题的一个考虑。使用不可变对象可以缓存它们,而不是每次都重新创建它们(即字符串),它对应用程序的性能有很大的帮助。

1

需要考虑的一件事:如果你打算在一个HashMap中使用一个类的实例作为键,或者如果你打算把它们放在一个HashSet中,那么使它们不可变是比较安全的。

HashMap和HashSet的这样的事实,即计数为对象的哈希码保持不变,只要对象是地图或设置。如果在HashMap中使用对象作为键,或者如果将其放入HashSet中,然后更改对象的状态以便hashCode()返回不同的值,那么您将HashMap或HashSet与你会得到奇怪的东西;例如,当您迭代地图或设置对象时,但是当您尝试获取它时,就好像它不在那里。

这是由于HashMap和HashSet如何在内部工作 - 它们通过散列码组织对象。

This article通过Java并发大师布赖恩戈茨给人的优点和不可变对象的缺点的一个很好的概述。

0

我想,如果你想分享不同的变量之间的同一个对象,它需要是不变的。

例如:Java中

String A = "abc"; 
String B = "abc"; 

String对象是不可改变的。现在A & B指向相同的“abc”字符串。 现在

A = A + "123"; 
System.out.println(B); 

它应该输出:

abc 

因为字符串是不可改变的,A就干脆指向新的“ABC123”字符串对象,而不是修改以前的字符串对象。

+0

-1。 A = A +“123”将简单地构建一个新字符串(abc123)并将其分配给变量A.字符串是不可变的,因为您不能将123字符串转换为abc - 字符串对象没有任何分配选项。然而,建立一个新的String对象是完全正确的。 – fwielstra 2010-09-23 13:47:43

+0

那么,建立一个新的字符串,A将指向NEW“abc123”。这里的重点领域是NEW对象的一个​​“点”。我没有说“abc123”是修改现有“abc”和“123”的结果。 – janetsmith 2010-09-29 05:25:10