2014-12-06 117 views
1

問題是我無法修改我從內部寫入的對象,從而創建了一個我修改並返回的克隆。但是,在其他函數中,我直接修改調用該方法的對象。我希望和人們建議,保持事物的一致性,因此用戶可以對返回的克隆執行任何他們喜歡的操作,而無需修改實際的對象。直接修改對象或返回對象的修改後的克隆會更好嗎?

我必須在某些函數中返回對象的修改後的克隆,因爲沒有辦法繞過它(我意識到)。除了標題中的問題,最好還是採取一致的做事方式(讓我爲即使是最輕微的變化返回克隆),還是可以,如果我有不同的方式來回復同一班級的用戶?

[編輯]它是更好地做到這一點:

public Image fillWithColor(Color fillColor) { 
    Image newImage = new Image(this.getWidth(), this.getHeight(), this.getType()); 

    for (int x = 0; x < this.getWidth(); x++) { 
     for (int y = 0; y < this.getHeight(); y++) { 
      newImage.setPixel(x, y, fillColor.getRGB()); 
     } 
    } 

    return newImage; 
} 

或本:

public void fillWithColor(Color fillColor) { 
    for (int x = 0; x < this.getWidth(); x++) { 
     for (int y = 0; y < this.getHeight(); y++) { 
      this.setPixel(x, y, fillColor.getRGB()); 
     } 
    } 
} 
+0

這實際上是一個重要的問題!不要投票結束。這基本上是對象是否應該是可變的問題。 – Ingo 2014-12-06 19:53:38

+0

這完全取決於你將如何處理下游對象,以及你採用什麼約定來使用這些對象。對於某些程序來說,答案可能更有效率,對其他程序來說則可能不對 – keshlam 2014-12-06 20:06:02

+0

我認爲返回一個「newImage」並仍然操作源代碼會很奇怪。我也認爲假設調用一個像「fillWithColor」這樣的對象不會返回任何內容的方法不會修改對象是非常糟糕的。 – nPn 2014-12-06 20:26:08

回答

1

一個大趨勢是把儘可能多的數據爲只讀,對各種可能的重要的原因。即使數據庫今天也這樣做。

您顯然已經認識到數據的不受控制的修改會讓您陷入困境。很好。嘗試將數據設計爲不可變對象,並且從長遠來看,很多事情會更容易。請注意,Java中的某些數據結構本質上是可變的(數組,散列表等),並且意味着並預期會發生變異。

在上面的例子中,我會選擇第一個變體。爲什麼?它可能花費幾微秒和一些RAM來複製圖像,而不是原地更新。但是你可以保留舊的圖像,並根據你的應用程序,這可能是有益的。此外,你可以在10個不同的線程中使用10種不同的填充顏色對同一圖像進行着色,並且不會出現鎖定問題。

這就是說,它仍然不可能回答你的問題,如「它總是更好......」。這取決於你的問題,你的環境,編程語言,你正在使用的庫和許多因素。

所以,假設不可變的數據在大多數情況下都是可取的,除非有嚴重的原因。 (在執行時間上節省幾個微秒通常不是一個嚴重的原因。)

換言之,應該有充分的理由使數據類型可變,而不變性應該是默認值。不幸的是,Java並不是支持這種方法的語言,相反,默認情況下所有東西都是可變的,並且需要一些努力才能使它不同。

+0

謝謝你長時間的解釋性迴應。正如你所建議的,我將修改方法來返回克隆,而不是直接修改圖片,因爲我不需要擔心速度和內存。 – sjbhalli 2014-12-06 20:28:50

1

唯一正確的答案是:

這取決於」。

這就是工程的全部內容。做出正確的折衷。假設你是一名爲城市工作的工程師,並負責爲市中心設計新垃圾箱。你有一些決定。你想讓它們變大,所以它們可以包含大量的垃圾,並且在繁忙的日子裏不會溢出。但是你也想讓它們變小,這樣它們就不會佔據人行道上的很多空間。你想讓它們變亮,這樣在倒空時可以很容易地處理它們,而且很重,所以它們不會被風吹過或被流氓踢掉。所以這不是大或小,重或輕的問題,但是多大和多重。

在軟件工程中,還有許多相互排斥的特性,您必須在您的項目中做出正確的選擇。一些示例:

  • 延遲與吞吐量 - 您是否想對請求做出快速反應,還是希望完成大量工作?
  • 內存vs cpu - 你可以使用大量的內存作爲查找表,或者你會刻錄CPU時間來計算答案。

永恆VS可變

不變類型的優點是它們是線程安全的。線程A和B都可以引用同一個對象,並且仍然可以確保它的值在不使用鎖的情況下不會意外更改。如果線程A想要更改該值,則可以通過將其保存的引用更改爲新對象來實現;線程B仍然很高興地抓住原始對象。

使對象的值意外改變意外不僅是併發編程中的問題,而且在您的課程的用戶不期待它時也會發生。這就是爲什麼有defensive copying的概念。

java中的股票Date類是可變的。所以考慮一個Person類與getBirthDate()獲得者和setBirthDate()二傳手。作爲Person課程的用戶,您希望只能使用setter更改人員的出生日期,但如果您的getter沒有返回防禦副本,那麼課程的用戶還可以通過更改它從getBirthDate()收到的Date對象。

所以不可變的類型使得程序線程安全(r)並且容易出錯,因此通常是一個好主意,但是你不能總是使用它們。您的fillWithColor()函數是一個實際上並不可行的例子。

A Canvas class是一個可變對象。您將擁有fillWithColor()功能,但也有drawLine(),drawElipse(),drawText()等等。用這些函數構建圖形可能需要很多調用。考慮繪製「no parking」交通標誌:

  1. 填充背景色
  2. 畫一個紅圈
  3. 畫一個白色圓圈裏面
  4. 畫裏面是
  5. 黑色P畫一個紅色的對角線穿過它

如果您的Canvas類是不可變的,您將需要五倍的內存量和p循環五次像素的數量。這實在是一個微不足道的例子。考慮this獵豹的SVG圖像。它背後的每個地方都是一個調用draw函數的函數。

這取決於

我要說的是,你應該在這裏你可以使用一成不變的類型和使用一成不變的,你不能。通常情況下,這種差異會隨着數據類型的變化而變化

如果您的類型引用的數據結構足夠小以成爲值類型,那麼它應該可能是不可變的引用類型。像java Date應該是不可變的,畢竟它只有8個字節。 如果你的類型引用了一些很大的東西,並且你需要對它進行很多操作,那麼你將不得不務實,並且使它成爲一個可變類型。像你的Canvas的例子,所有的圖像可能是兆字節大。

需要可變且不可變的。

+0

因此,爲了節省內存和時間,你會建議讓我的對象可變,並仍然有某些函數返回而不是直接修改? – sjbhalli 2014-12-06 23:02:40

+0

我同意「這取決於」。我建議你盡一切可能使它明顯是不變的,而不是。除了改變方法返回無效和非變異方法返回'克隆'對象,我會選擇一種方法命名約定,使行爲顯而易見。例如,更改畫布背景的方法可能被稱爲「setBackgroundColor」,而克隆畫布並設置克隆背景的方法可能被稱爲「withBackgroundColor」,以便獲得「canvas.setBackgroundColor(color)」vs 。「canvas.withBackgroundColor(color)」 – GreyBeardedGeek 2014-12-06 23:23:27

+0

@sjbhalli是的,使'fillWithColor()'變異函數被調用的對象上的像素。我不知道更多(這取決於)我不能告訴其他功能。其他的繪製函數應該會發生變化,但是perhaos在那裏有一個出生日期屬性,然後你應該有一個返回防禦副本的getter。 – 2014-12-07 09:34:51