2009-08-16 155 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