跳轉到內容

面向物件程式設計/狀態之惡

來自華夏公益教科書

"狀態"是邪惡的!

[編輯 | 編輯原始碼]

我們沒有足夠強調的一點:狀態(與變化相反)是邪惡的!或者,(也許更好的說法)維護不必要的狀態是所有(呃……許多)錯誤的根源。讓我們看一個例子,現在用 Ruby

  1. 我們正在隨時間顯示某些內容,因此我們正在處理
  2. 畫素和秒。根據縮放級別,
  3. 它們之間存在相關性:每秒畫素,
  4. 或 PPS。

類時間線

{
 PPS;
 current_position_in_seconds;
 current_position_in_pixels;
public:
 GetPosInSeconds() {current_position_in_seconds;}
 SetPosInSeconds(s) {current_position_in_seconds = s;} # oops, we're out of sync
 GetPosInPixels() {current_position_in_pixels;}
 SetPosInPixels(p) {current_position_in_pixels = p;} #oops, we're out of sync

} 在此示例中,我們正在維護不必要的狀態——也就是說,我們同時以秒和畫素來儲存位置。這對使用此類的使用者來說很方便,也很重要,但我們在封裝方面搞砸了。每當您在一個單位中設定值時,都會破壞另一個單位的正確性。您說,這是一個簡單的修復方法

  1. 我們正在隨時間顯示某些內容,因此我們正在處理
  2. 畫素和秒。根據縮放級別,
  3. 它們之間存在相關性:每秒畫素,
  4. 或 PPS。

類時間線 {

 PPS;
 current_position_in_seconds;
 current_position_in_pixels;

public

 GetPosInSeconds() {current_position_in_seconds;}
 SetPosInSeconds(s)
      {
         current_position_in_seconds = s;
         current_position_in_pixels = s*PPS;
       }
 GetPosInPixels() {current_position_in_pixels;}
 SetPosInPixels(p)
      {
         current_position_in_pixels = p;
         current_position_in_seconds = p/PPS;
      }

} 這修復了前面示例中明顯的錯誤,但您為自己製造了很多工作。現在您必須在 Timeline 類中的每個方法中保留所有計算的副本以保持一致性。這裡可能還有幾十個這樣的情況(相信我)。那麼,當您新增其他單位時,例如英寸?您必須再次複製所有內容。大概您會明白這將走向何方:計算屬性、邏輯屬性、虛擬屬性,無論您喜歡怎麼稱呼它們。讓我們再次看看這個例子

類時間線 {

 PPS; # Pixels Per Second
 IPS; # Inches Per Second
 current_position_in_seconds;

public

 GetPosInSeconds() {current_position_in_seconds;}
 SetPosInSeconds(s) {current_position_in_seconds = s;}

 GetPosInPixels() {current_position_in_seconds*PPS;}
 SetPosInPixels(p) {current_position_in_seconds = p/PPS; }

 GetPosInInches() {current_position_in_seconds*IPS;}
 SetPosInInches(i) {current_position_in_seconds = i/IPS; }

} 現在,我們假設 PPS 和 IPS 的轉換因子設定正確,以方便起見,但除此之外,我們已經大大簡化了問題。在我們現在編寫的另外二十個函式中,我們只需要擔心秒,沒有一致性問題需要擔心。此外,如果我們需要將基本單位從秒更改為畫素,Timeline 使用者永遠不會知道。

華夏公益教科書