改善代碼設計 —— 組織好你的數據(Composing Data)

作者: Create Chen  來源: 博客園  發布時間: 2011-05-31 21:57  閱讀: 2379 次  推薦: 0   原文鏈接   [收藏]  

  系列博客

      1. 改善代碼設計 —— 優化函數的構成(Composing Methods)

      2. 改善代碼設計 —— 優化物件之間的特性(Moving Features Between Objects)

      3. 改善代碼設計 —— 組織好你的數據(Composing Data)

      4. 改善代碼設計 —— 簡化條件表達式(Simplifying Conditional Expressions)

      5. 改善代碼設計 —— 簡化函數調用(Making Method Calls Simpler)

      6. 改善代碼設計 —— 處理概括關系(Dealing with Generalization)

  1. Self Encapsulate Field (自封裝值域)

  解釋:

      大部分類 (class) 中都會有一些值域 (field), 隨之還會有一些方法使用到了這些值域. "如果調用這些值域"這個問題分為兩種觀點: 1. 應該直接調用它們 2. 應該通過訪問函數調用它們.

      我覺得大部分情況下直接調用比較方便, 過多的訪問函數還會造成類中的函數過多, 當然將來如果我覺得直接調用帶來了一些問題, 寫一個一個的訪問函數也并不是很困難.

      下面的例子主要說明如何給值域寫一個訪問函數, 并通過訪問函數調用值域的值.

  沖動前:

 
private string _userName, _password;

public bool IsValid()
{

bool isValid = !(String.IsNullOrEmpty(_userName) &&
String.IsNullOrEmpty(_password));
return isValid;
}

  沖動后:

 
private string _userName, _password;

public bool IsValid()
{

bool isValid = !(String.IsNullOrEmpty(GetUserName()) &&
String.IsNullOrEmpty(GetPassword()));
return isValid;
}

private string GetUserName()
{

return _userName;
}

private string GetPassword()
{

return _password;
}

  2. Replace Data Value with Object (以物件取代數據值)

  解釋:

      如果你發現代碼中有很多字段或者值域似乎都在描述某一樣東西, 可以考慮將這些字段或者值域封裝到一個類中, 用這個物件代替原先代碼中繁雜的字段和值域.

  沖動前:

 
class Order
{

public string CustomerName { get; set; }
public string CustomerAddress { get; set; }
public int CreditLevel { get; set; }
public int CustomerTel { get; set; }
//...
}

  沖動后:

 
class Order
{

public Customer Customer { get; set; }
}


class Customer
{

public string CustomerName { get; set; }
public string CustomerAddress { get; set; }
public int CreditLevel { get; set; }
public int CustomerTel { get; set; }
//...
}

  3. Change Value to Reference (將值對象改為引用對象)

  解釋:

      一個類中有時包含值對象作為它的字段或者屬性, 如訂單 Order 類中包含了一個 客戶 customerName 的字段. 如果同一個客戶有好幾份訂單, 那么每一份訂單就都會保存一次客戶的姓名. 如果這個客戶的姓名改變了, 那么就需要更改每份個訂單中的 customerName.
      如果將 customerName 提取出去, 提煉一個 Customer 的類, 訂單使用的是 Customer 實例的引用, 則只需更改實例中客戶姓名的屬性, 因為所有的訂單都是引用的這個客戶實例, 所以它們不需要作其它更改.

  沖動前:

 
class Order
{

private string _customerName;

public Order(string customerName)
{

this._customerName = customerName;
}
}

  沖動后:

 
class Order
{

private Customer _customer;

public Order(Customer customer)
{

this._customer = customer;
}
}


class Customer
{

public string CustomerName { get; set; }
}

  4. Change Reference to Value (將引用對象改為值對象)

  解釋:

      如果給一個類傳遞引用類型變得不是你想要的效果, 比如我曾經做過一個類似于 MSN 的軟件, 其中將人的頭像, 昵稱, 個性簽名顯示在一個自定義控件中, 然后在使用了 ListBox.Add(myControl), 結果我驚奇的發現最后好友列表里居然都是同一個 (最后一個) 人. 原來 ListBox.Add() 傳遞進去的都是引用的同一個 myControl 實例, 盡管代碼貌似在動態的生成每一個人, 但其實都只修改了一個 myControl, 而 ListBox 僅僅了引用這一個對象. 后來我在代碼中動態的生成不同對象的實例才解決了這個問題.

      除了這樣, 還可以在你的類中定義一個值對象, 每一次生成一個類的實例時, 類中值對象的控制權總是屬于自己.

  5. Replace Array with Object (用物件取代數組)

  解釋:

      一個類中可能會包含很多字段, 你不能因為這些字段都是 string 類型, 就通通把它們放到一個 string[] 里, 在類的方法體中通過數組索引來獲取你需要的值, 這樣很難讓別人記得你 string[0] 表示的是姓名, string[1] 表示的是他的住址...

      下面的將展示前后代碼可讀性的差距.

  沖動前:

 
string[] _person = new string[5];

public string GetName()
{

return _person[0];
}

public string GetAdd()
{

return _person[1];
}

  沖動后:

 
private Person _person;

public string GetName()
{

return _person.Name;
}

public string GetAdd()
{

return _person.Address;
}

  6. Duplicate Observed Data (復制被監視數據)

  解釋:

      一個良好的系統應該事先業務邏輯 (Business Logic) 與用戶界面 (User Interface) 分離, 如果沒有這樣做, 常見的比如在 Program.cs 堆疊了大量的代碼, 業務邏輯與用戶界面非常緊密的耦合在了一起. 實現業務邏輯與用戶界面分離, 最重要的實現是數據的同步機制, Duplicate Observed Data 重構手段用來完成這項工作. 由于例子較長, 可以獨立成篇, 在以后介紹觀察者模式 (Observer Pattern) 的時候會搬出來討論. 大笑

  7. Change Unidirectional Association to Bidirectional (將單向關聯改為雙向)

  解釋:

      如果兩個 Class 都需要使用對方的特性, 但兩者之間只有一條單向關聯 (如只有調用方才能調用被調用的特性), 如果你想讓被調用的那一方知道有哪些物件調用了自身, 這時就需要使用 Change Unidirectional Association to Bidirectional.

      通常的做法是在被調用方的代碼里定義一個被調用方類型的集合 (Collection), 如 ArrayList<T>, List<T>(推薦使用 List<T>), 在[調用方] 調用 [被調用方] 的時候, 在 [被調用方] 里的集合中也添加一下自己的引用.

  8. Change Bidirectional Association to Unidirectional (將雙向關聯改為單向)

  解釋:

      使用了Change Unidirectional Association to Bidirectional 之后, 如果需求變了, 發現雙向關聯是沒有必要的, 這時就需要將雙向關聯改為單向, 也就是 Change Bidirectional Association to Unidirectional, 步驟與 Change Unidirectional Association to Bidirectional 相反.

  9. Replace Magic Number with Symbolic Constant (用符號常量取代魔法數)

  解釋:

      新手在寫程序的時候, 往往不注重命名 (name), 他們對程序的要求也就是能正確運行即可. 在 IDE 中隨便拖幾個 Button, 也不重新命名, 于是在代碼中出現了類似 Button1, Button2, Button3...之類的, 你無法光看代碼就能想象得出 Button1 對應 UI 中的哪一個按鈕. 同樣的壞習慣出現在代碼的字段, 臨時變量的命名, 到處是 a, aa, aaa...

      使用 Replace Magic Number with Symbolic Constant 吧! 如果你的 Button 是用于"發生", 那么你可以給它一個名字 —— btnSend; 如果你的一個 Int32型 變量用于表示一個人的年齡, 請給它一個名字 —— age.

  10. Encapsulate Field (封裝值域)

  解釋:

      如果你的 class 中存在一個 public 字段, 請將它設置為屬性.

      面向對象的第一條原則就是封裝 (encapsulation), 或者稱之為數據隱藏 (Data Hiding), 如果一個字段想被外界訪問到, 你應該將它設為屬性, 在 C# 中設置屬性比在 Java 中要方便許多, 因為 C# 編譯器幫你做了一些額外工作.

  沖動前:

 
public string _name;

  沖動后:

 
public string Name { get; set; }

  11. Encapsulate Collection (封裝集合)

  解釋:

      如果你的 class 中有一個方法需要返回多個值, 在類中有一個集合 (Collection) 暫時保持這些返回值, 有些情況下應當避免讓 [調用端] 直接訪問這個集合. 如果 [調用端] 修改了你集合的某一項, 而 [被調用端] 不知道自己的集合被修改了, 而另外的一些方法仍然在調用被修改后的集合, 這樣可能會造成無法預料的后果.

      Encapsulate Collection 建議你在 [被調用端] 將這個集合封裝 (至少你將它的訪問權限設置為 private) 起來, 有需要的話, 并提供修改這個集合的函數.

  12. Replace Record with Data Class (用數據取代記錄)

  解釋:

      與第二條 Replace Data Value with Object 和第五條 Replace Array with Object 做法相似, 目的是提取類中的數據到一個描述記錄 (Record) 的類中, 方便以后可以對這個類進行擴展.

  13. Replace Subclass with Fields (用值域取代子類)

  解釋:

      子類建立在父類之上再增加新的功能或者重載父類可能的變化行為, 有一種變化行為 (variant behavior) 成為常量函數 (constant method), 他們會返回一個硬編碼 (hard-coded) 值, 這個值一般有這樣的用途: 讓不同的父類中的同一個訪問函數返回不同的值, 你可以在父類中將訪問函數聲明為抽象函數, 并在不同的不同的子類中讓它返回不同的值. 但如果子類中只有返回常量的函數, 沒有其它的作用, 往往子類并沒有太大的存在價值.

      你可以在父類中設計一些與子類返回值相應的值域, 再對父類做一些其它修改, 從而可以消除這些子類, 好處是避免不必要的子類帶來的復雜性, 這就是 Replace Subclass with Fields.

  沖動前:

 
class Person
{

protected abstract bool isMale();
protected abstract char Code();
//...
}
class Male : Person
{

protected override bool isMale()
{

return true;
}

protected override char Code()
{

return 'M';
}
}

class Female : Person
{

protected override bool isMale()
{

return false;
}

protected override char Code()
{

return 'F';
}
}

  沖動后:

 
class Person
{

public bool IsMale { get; set; }
public char Code { get; set; }

public Person(bool isMale, char code)
{

this.IsMale = IsMale;
this.Code = code;
}

public Person Male()
{

return new Person(true, 'M');
}

public Person Female()
{

return new Person(false, 'F');
}

//...
}

      調用的時候這樣: Person Create_Chen = Person.Male();

0
0
 
標簽:重構
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()