[.net 面向對象編程基礎] (14) 重構

[.net 面向對象編程基礎] (14) 重構html

      經過面向對象三大特性:封裝、繼承、多態的學習,能夠說咱們已經掌握了面向對象的核心。接下來的學習就是如何讓咱們的代碼更優雅、更高效、更易讀、更易維護。固然了,這也是從一個普通程序員到一個高級程序員的必由之路。就看病同樣,普通醫生只能治標,高級醫生不但看好病,還能除病根。程序員

1.什麼時重構?編程

重構(Refactoring)就是在不改變軟件現有功能的基礎上,經過調整程序代碼改善軟件的質量、性能,使其程序的設計模式和架構更趨合理,提升軟件的擴展性和維護性。設計模式

目的:是提升其可理解性,下降其修改爲本。架構

通俗的說法就是,程序的功能和結果沒有任何的變化。重構只是對程序內部結構進行調整,讓代碼更加容易理解,而後更容易維護。也就是代碼的優化。ide

經過上述定義,能夠看出,重構並非.net的自己的特性,而是軟件設計範疇。函數

2.重構的目的性能

 A.改進軟件的設計學習

   在實際工做中,爲了趕進度或是爲了短時間利益,再或者是沒有徹底摸清軟件總體架構的狀況下,對代碼進行改動。而這些改動的積累很容易使軟件偏離它原先的設計初衷,使軟件變很很難維護或沒法維護。優化

而重構能夠幫助從新組織代碼,從新清晰的體現結構和進一步改進設計。

B.提升代碼的質量和可維護性

容易理解的代碼很容易維護和作進一步開發。即便寫這些代碼的程序員自己而言,容易更解的代碼也能幫助他容易的修改。

代碼也是文檔,首先是寫給人看的,其次纔是計算機。

C.幫助儘早的發現錯誤 

    重構是一個複習和反饋的過程,在另外一個時段從新審視本身或別人的代碼,能夠更容易發現問題和加深對代碼的理解.

重構是一個良好的開發習慣。

D.能夠提升開發速度

重構對設計和代碼的改進,均可以有效提升開發速度。

在一個有缺陷的設計和混亂的代碼基礎上開發,即便表面是進度較快,但本質是延後對設計缺陷的發現和對錯誤的修改。也就延後了開發風險,最終要在開發後期付出更多的代價。

一句話,出來混,早晚是要還的!!

3.重構的時機

重構的時候,即什麼時候須要重構,什麼時候不須要

A.首先,如下幾種狀況須要重構:

過大的類和過長的方法

過長的方法因爲包含的邏輯過於複雜,錯誤機率將直線上升,而可讀性則直線降低,類的健壯性很容易被打破。當看到一個過長的方 法時,須要想辦法將其劃分爲多個小方法,以便於分而治之。

牽一髮而須要動全身的修改

  當你發現修改一個小功能,或增長一個小功能時,就引起一次代碼地震,也許是你的設計抽象度不夠理想,功能代碼太過度散所引發的。

類之間須要過多的通信

  A類須要調用B類的過多方法訪問B的內部數據,在關係上這兩個類顯得有點狎暱,可能這兩個類本應該在一塊兒,而不該該分家。

過分耦合的信息鏈

  若是你在代碼中看到須要獲取一個信息,須要一個類的方法調用另外一個類的方法,層層掛接,就象輸油管同樣節節相連。這每每是由於銜接層太多形成的,須要查看就否有可移除的中間層,或是否能夠提供更直接的調用方法。

各自爲政的功能模塊

  若是你發現有兩個類或兩個方法雖然命名不一樣但卻擁有類似或相同的功能,你會發現每每是由於開發團隊成員協調不夠形成的。筆者曾經寫了一個頗好用的字符串處理類,但由於沒有及時通告團隊其餘人員,後來發現項目中竟然有三個字符串處理類。革命資源是珍貴的,咱們不該各立山頭幹革命。

不完美的設計  

  每一個系統都或多或少存在不完美的設計,剛開始可能注意不到,到後來纔會慢慢凸顯出來,此時惟有敢於更改纔是最好的出路。

缺乏必要的註釋

  雖然許多軟件工程的書籍常提醒程序員須要防止過多註釋,但這個擔憂好象並無什麼必要。每每程序員更感興趣的是功能實現而非代碼註釋,由於前者更能帶來成就感,因此代碼註釋 每每不是過多而是過少,過於簡單。人的記憶曲線降低的坡度是陡得嚇人的,當過了一段時間後再回頭補註釋時,很容易發生"提筆忘字,愈言且止"的情形。

曾在網上看到過微軟的代碼註釋,其詳盡程度讓人歎爲觀止,也從中體悟到了微軟成功的一個經驗。

(以上關於重構的內容來自網上小夥伴的分析,仍是比較全面的,摘錄過來分享之)

B.還有幾種狀況是不適用重構的:

代碼混亂,錯誤百出,這種狀況,不是重構而是須要重寫了

大型多模塊軟件,須要逐步重構,不是一會兒完成

重構須要太長的時間,這種狀況下不建議重構。

項目即將進入交付階段,隱定性賽過其它。

3.如何進行重構

前面講了太多的理論知識,下面來點硬貨,說說重構的方法。

3.1使用VS.NET 自身的功能實現快速重構

VS.net自己關於重構的功能,可能不少人不多用到,做爲一個重構的輔助功能,雖然說不能徹底實現重構,可是能夠幫助咱們快速優化代碼。

3.1.1重構類型

<1>. 重命名

<2>.提取方法 

<3>. 封裝字段

<4>. 提取接口

<5>. 將局部變量提高爲參數

<6>. 移除參數

<7>. 從新排列參數

VS.NET中提供了這麼七種重構的類型。咱們在代碼編輯窗口中,點擊鼠標右鍵,能夠看到以下圖所示:

  

下面,咱們逐一說明

<1>重命名

咱們在代碼重構過程當中,會有不按規範命名的狀況發生或者咱們想讓一段代碼產生一個副本。

A. 提供了一種重命名代碼符號(如字段、局部變量、方法、命名空間、屬性和類型)標識符的簡單方法.

B. 「重命名」功能除了可用來更改標識符的聲明和調用之外,還可用來更改註釋中和字符串中的名稱.

以下圖所示,選中一個名稱後,輸入新名稱,VS.NET會提示你更改那些名字。

 

<2>.提取方法

能夠經過從現有成員的代碼塊中提取選定的代碼來建立新方法.

B. 建立的新方法中包含選定的代碼,而現有成員中的選定代碼被替換爲對新方法的調用.

C. 代碼段轉換爲其本身的方法,使您能夠快速而準確地從新組織代碼,以得到更好的重用和可靠性.

• 優勢

A. 經過強調離散的可重用方法鼓勵最佳的編碼作法。

B. 鼓勵經過較好的組織得到自記錄代碼。當使用描述性名稱時,高級別方法能夠像讀取一系列註釋同樣進行讀取。

C. 鼓勵建立細化方法,以簡化重載。

D. 減小代碼重複.

以下圖,咱們選中一個方法中的代碼片斷,點重構中的 「提取方法」彈出下下對話框,咱們重命名一個新的方法名

 

肯定後,以下所示:

 

生成一個靜態的方法。在一個方法實現中代碼片斷太長的時候,咱們能夠很方便的進行方法提取了。

<3>. 封裝字段

A. 能夠從現有字段快速建立屬性,而後使用對新屬性的引用無縫更新代碼.

B. 當某個字段爲publicC# 參考)時,其餘對象能夠直接訪問該字段並對其進行修改,而不會被擁有該字段的對象檢測到。經過使用屬性(C# 編程指南)封裝該字段,能夠禁止對字段的直接訪問。

C. 僅當將光標與字段聲明置於同一行時,才能夠執行「封裝字段」操做。

• 實例

大部分開發者都習慣把類級的變量(字段)暴露給外界。因爲每個對象都屬於面向對象編程,因此開發者應該容許經過屬性或方法來存取變量。這種狀況可使用重構菜單下的"封裝字段"選項來進行處理。

爲此,選擇你想包裝在一個屬性中的類級變量而且選擇"封裝字段"選項。這將打開一個以下圖所示的對話框:

你須要輸入該屬性的名字而且決定是否你想從類外或類內部更新到該變量的參考。就象"重命名"對話框同樣,你能夠在應用以前先預覽一下所做的改變。

以下圖所示,假如咱們要在動物這個類中,加一個屬性,咱們使用封裝字段,

 

 

若是選擇「外部」肯定後,代碼以下:

 

能夠看到,爲咱們自動增長了一個外部屬性

<4>• 提取接口

A. 使用來自現有類、結構或接口的成員建立新接口的簡單方法.

B. 當幾個客戶端使用類、結構或接口中成員的同一子集時,或者當多個類、結構或接口具備通用的成員子集時,在接口中嵌入成員子集將頗有用.

C. 僅當將光標定位於包含要提取成員的類、結構或接口中時,才能夠訪問此功能。當光標處於此位置時,調用「提取接口」重構操做.

以下圖所示,咱們在類名稱點擊右鍵 重構,選擇提取接口,在彈出窗口中,輸入接口名稱,選擇類的公有成員,則爲它們建立了一個接口文件,很是實用。

 

<5>• 將局部變量提高爲參數

A. 提供一種簡單的方法,以在正確更新調用站點的同時將變量從局部使用移動至方法、索引器或構造函數參數.

B. 調用「將局部變量提高爲參數」操做時,變量將被添加到成員參數列表的結尾處.

C. 對已修改爲員的全部調用都將使用新參數(將替代最初賦給該變量的表達式)當即進行更新,並保留代碼,以使其像變量提高以前那樣正常工做.

D. 將常數值賦值給提高的變量時,此重構操做效果最好。必須聲明並初始化該變量,而不能僅聲明或僅賦值.

• 實例

原代碼:

private static void NewMethod2()
{
        string s = "";
}

選中s,轉換後

private static void NewMethod2(string s)
{ 
} 

 

<6>• 移除參數

A. 從方法、索引器或委託中移除參數的簡單方法.

B. 在調用成員的任何位置,都會將參數移除以反映新聲明.

• 實例

原代碼    

protected void Page_Load(EventArgs e, object sender)
{
        int i = 0;
        NewMethod2("1","2");
}

private static void NewMethod2(string s1, string s2)
{
        string s = s1 + s2;
 }

移除後的代碼   

 protected void Page_Load(EventArgs e, object sender)
{
        int i = 0;
        NewMethod2();
 }

 private static void NewMethod2()
{
        string s = s1 + s2;
}

 

<7>• 從新排列參數

A. 對方法、索引器和委託的參數順序進行更改的簡單方法.

B. 能夠經過方法聲明或方法調用來從新排列參數。要將光標置於方法聲明或委託聲明中,而不是置於正文中。

• 實例

原代碼:

private static void NewMethod2(string s1,string s2)
{
}

從新排列後

private static void NewMethod2(string s2,string s1)
{

}

4.重構實例

 咱們經過一個實例來看看重構帶來的好處,仍是咱們前一節的關於動物叫的例子,有一個基類 動物(Animal)有成員屬性名字(Name

方法叫聲(Shout)和叫的次數的虛方法(getShoutCount),它有N個派生類,咱們先看重構前的代碼以下:

  1 /// <summary>
  2 /// 動物類(父類)
  3 /// </summary>
  4 class Animal
  5 {
  6     /// <summary>
  7     /// 名字
  8     /// 說明:類和子類可訪問
  9     /// </summary>
 10     protected string name;
 11 
 12 
 13     /// <summary>
 14     /// 構造函數
 15     /// </summary>
 16     /// <param name="name"></param>
 17     public Animal(string name)
 18     {
 19         this.name = name;
 20     }
 21 
 22     private int shoutNum = 3;
 23     public int ShoutNum
 24     {
 25         get { return shoutNum; }
 26         set { shoutNum = value; }
 27     }
 28 
 29     /// <summary>
 30     /// 名字(虛屬性)
 31     /// </summary>
 32     public virtual string MyName
 33     {
 34         get { return this.name; }
 35 
 36     }
 37 
 38     /// <summary>
 39     /// 叫(虛方法)
 40     /// </summary>
 41     public virtual void Shout()
 42     {
 43         Console.WriteLine("我會叫!");
 44     }
 45 
 46 }
 47 
 48 /// <summary>
 49 /// 狗(子類)
 50 /// </summary>
 51 class Dog : Animal
 52 {
 53     string myName;
 54     public Dog(string name)
 55         : base(name)
 56     {
 57         myName = name;
 58     }
 59 
 60     /// <summary>
 61     /// 名字(重寫父類屬性)
 62     /// </summary>
 63     public override string MyName
 64     {
 65         get { return "我是:狗狗,我叫:" + this.name; }
 66     }
 67 
 68     /// <summary>
 69     /// 叫(重寫父類方法)
 70     /// </summary>
 71     public override void Shout()
 72     {
 73         string result = "";
 74         for (int i = 0; i < ShoutNum; i++)
 75             result += "汪!";
 76         Console.WriteLine(result);
 77     }
 78 }
 79 /// <summary>
 80 /// 貓(子類)
 81 /// </summary>
 82 class Cat : Animal
 83 {
 84     string myName;
 85     public Cat(string name)
 86         : base(name)
 87     {
 88         myName = name;
 89     }
 90     /// <summary>
 91     /// 名字(重寫父類屬性)
 92     /// </summary>
 93     public override string MyName
 94     {
 95         get { return "我是:貓咪,我叫:" + this.name; }
 96 
 97     }
 98 
 99     /// <summary>
100     /// 叫(重寫父類方法)
101     /// </summary>
102     public override void Shout()
103     {
104         string result = "";
105         for (int i = 0; i < ShoutNum; i++)
106             result += "喵!";
107         Console.WriteLine(result);
108     }
109 }
110 
111 /// <summary>
112 /// 羊(子類)
113 /// </summary>
114 class Sheep : Animal
115 {
116     string myName;
117     public Sheep(string name)
118         : base(name)
119     {
120         myName = name;
121     }
122     /// <summary>
123     /// 名字(重寫父類屬性)
124     /// </summary>
125     public override string MyName
126     {
127         get { return "我是:羊羊,我叫:" + this.name; }
128 
129     }
130 
131     /// <summary>
132     /// 叫(重寫父類方法)
133     /// </summary>
134     public override void Shout()
135     {
136         string result = "";
137         for (int i = 0; i < ShoutNum; i++)
138             result += "咩!";
139         Console.WriteLine(result);
140     }
141 }

 

咱們能夠看到,雖然這段代碼實現了繼承和多態,封裝的特性,代碼仍是比較簡潔的,可是有一點就是這個叫的方法,每一個子類中都要寫一次循環。假如又來了豬啊,牛啊,這些動物,是否是代碼量也很多啊。咱們能不能只寫一次循環呢,答案是確定的,看咱們重構後的代碼:

  1 /// <summary>
  2 /// 動物類(父類)
  3 /// </summary>
  4 class Animal
  5 {
  6     /// <summary>
  7     /// 名字
  8     /// 說明:類和子類可訪問
  9     /// </summary>
 10     protected string name;
 11 
 12     /// <summary>
 13     /// 構造函數
 14     /// </summary>
 15     /// <param name="name"></param>
 16     public Animal(string name)
 17     {
 18         this.name = name;
 19     }
 20 
 21     private int shoutNum = 3;
 22     public int ShoutNum
 23     {
 24         get { return shoutNum; }
 25         set { shoutNum = value; }
 26     }
 27 
 28     /// <summary>
 29     /// 名字(虛屬性)
 30     /// </summary>
 31     public virtual string MyName
 32     {
 33         get { return this.name; }
 34 
 35     }
 36 
 37     /// <summary>
 38     /// 叫聲,這個方法去掉虛方法,把循環寫在這裏
 39     /// </summary>
 40     public void Shout()
 41     {
 42         string result = "";
 43         for (int i = 0; i < ShoutNum; i++)
 44             result += getShoutSound()+"";
 45 
 46         Console.WriteLine(MyName);
 47         Console.WriteLine(result);
 48     }
 49     /// <summary>
 50     /// 建立一個叫聲的虛方法,子類重寫
 51     /// </summary>
 52     /// <returns></returns>
 53     public  virtual string  getShoutSound()
 54     {
 55         return "";
 56     }           
 57 }
 58 
 59 /// <summary>
 60 /// 狗(子類)
 61 /// </summary>
 62 class Dog : Animal
 63 {
 64     string myName;
 65     public Dog(string name): base(name)
 66     {
 67         myName = name;
 68     }
 69     /// <summary>
 70     /// 名字(重寫父類屬性)
 71     /// </summary>
 72     public override string MyName
 73     {
 74         get { return "我是:狗狗,我叫:" + this.name; }
 75     }        
 76     /// <summary>
 77     /// 叫(重寫父類方法)
 78     /// </summary>
 79     public override string getShoutSound()
 80     {
 81         return "汪!";           
 82     }
 83 }
 84 /// <summary>
 85 /// 貓(子類)
 86 /// </summary>
 87 class Cat : Animal
 88 {
 89     string myName;
 90     public Cat(string name): base(name)
 91     {
 92         myName = name;
 93     }
 94     /// <summary>
 95     /// 名字(重寫父類屬性)
 96     /// </summary>
 97     public override string MyName
 98     {
 99         get { return "我是:貓咪,我叫:" + this.name; }
100     }
101     /// <summary>
102     /// 叫(重寫父類方法)
103     /// </summary>
104     public override string getShoutSound()
105     {
106         return "喵!";
107     }
108 }
109 
110 /// <summary>
111 /// 羊(子類)
112 /// </summary>
113 class Sheep : Animal
114 {
115     string myName;
116     public Sheep(string name): base(name)
117     {
118         myName = name;
119     }
120     /// <summary>
121     /// 名字(重寫父類屬性)
122     /// </summary>
123     public override string MyName
124     {
125         get { return "我是:羊羊,我叫:" + this.name; }
126     }
127     /// <summary>
128     /// 叫(重寫父類方法)
129     /// </summary>
130     public override string getShoutSound()
131     {
132         return "咩!";
133     }
134 }

 

這樣重構,是否是代碼量就少不少了,結構也更加清晰了。。

調用一:            

//調用
Animal sheep = new Sheep("美羊羊");
sheep.Shout();
Console.ReadLine();

結果以下:

//調用結果
//我是:羊羊,我叫:美羊羊
//咩!咩!咩!

調用二:          

//調用
Animal dog= new Dog("旺財");
dog.Shout();
Console.ReadLine();

結果以下: 

//調用結果
//我是:狗狗,我叫:旺財
//汪!汪!汪!

 

總結:重構是一門複雜的學問,本節內容只是重構的皮毛而已,有一些書籍用幾千頁的篇幅來介紹中重構。可否熟練使用重構,寫出優雅高效的代碼是區分一個程序員優秀的標準之一,重構也是學習設計模的基礎,這須要咱們不斷的練習和思考才能作好。

 

要點:

A.重構(Refactoring)就是在不改變軟件現有功能的基礎上,經過調整程序代碼改善軟件的質量、性能,使其程序的設計模式和架構更趨合理,提升軟件的擴展性和維護性。

B.重構不是.NET面向對象自己的特性,而屬於一種軟件設計範疇。

C.重構提升了代碼的可讀性,可維護性;也使得代碼結構更加清晰。

D.可否有效的重構代碼,是一個程序員優秀與否的標準之一。也是學習設計模式和軟件架構的基礎。

E.重構是一門代碼藝術。 

============================================================================================== 

返回目錄

 <若是對你有幫助,記得點一下推薦哦,有不明白的地方或寫的不對的地方,請多交流>
 

==============================================================================================  

相關文章
相關標籤/搜索