返回總目錄 本小節目錄 Extract BaseClass(提煉基類) Extract Interface(提煉介面) Collapse Hierarchy(摺疊繼承體系) 7Extract BaseClass(提煉基類) 概要 兩個類有相似特性。為這兩個類建立一個基類,將相同特性移至基類。 動機 ...
本小節目錄
7Extract BaseClass(提煉基類)
概要
兩個類有相似特性。為這兩個類建立一個基類,將相同特性移至基類。
動機
重覆代碼是系統中最糟糕的東西之一。如果你在不同地方做同一件事情,一旦需要修改那些動作,你就得平白做更多的修改。
重覆代碼的某種形式就是:兩個類以相同的方式做類似的事情,或者以不同的方式做類似的事情。對象提供了一種簡化這種情況的機制,那就是繼承。但是,在建立這些具有共通性的類之前,你往往無法發現這樣的共通性,因此常常會在具有共通性的類出現之後,再開始建立其間的繼承結構。
範例
Dog類中的EatFood和Groom有可能被其他類用到,因為他們都是動物的一些公有性質,所以這個時候我們就會考慮對它進行提煉。
public class Dog { public void EatFood() { // eat some food } public void Groom() { // perform grooming } }
代碼如下所示,提取了Animal方法來封裝公用的EatFood和Groom類,從而使其他繼承了Animal類的子類都可以使用這兩個方法了。
public class Animal { public void EatFood() { // eat some food } public void Groom() { // perform grooming } } public class Dog : Animal { }
小結
這個重構是典型的繼承用法,很多程式員都會選擇這樣做,但是要註意正確的使用,不要造成過度使用了繼承,如果過度使用了,請考慮用介面、組合和聚合來實現。
這個手法也經常用到,比如做的MVC項目中的BaseController。
8Extract Interface(提煉介面)
概要
若幹客戶使用類介面中的同一子集,或者兩個類的介面有部分相同。將相同的子集提煉到一個獨立介面中。
動機
如果某個類在不同環境下扮演截然不同的角色,使用介面就是個好主意。你可以針對每個角色以Extract Interface提煉出相應介面。另一種可以用Extract Interface的情況是:你想要描述一個類的外部依賴介面(outbound interface,即這個類要求服務提供方提供的操作)。如果你打算將來加入其它種類的服務對象。只需要求它們實現這個介面即可。
範例
TimeSheet類表示員工為客戶工作的時間表,從中可以計算客戶應該支付的費用。為了計算這些費用,TimeSheet需要知道員工級別,以及該員工是否具有特殊技能:
class TimeSheet { public double Charge(Employee emp, int days) { int baseCharge = emp.GetRate() * days; if (emp.HasSpecialSkill()) { return baseCharge * 1.05; } return baseCharge; } } class Employee { public int GetRate() { return 2; } public bool HasSpecialSkill() { return true; } }
除了提供員工的級別和特殊技能信息之外,Employee還有很多其他方面的功能,但本應用程式只需這兩項功能。可以針對這兩項功能定義一個介面,從而強調“我只需要這部分功能”的事實:
public interface IBillable { int GetRate(); bool HasSpecialSkill(); }
然後,聲明讓Employee實現這個介面:
class Employee : IBillable { public int GetRate() { return 2; } public bool HasSpecialSkill() { return true; } }
完成以後,修改TimeSheet類中Charge()函數聲明,強調該函數只使用Employee的這一部分行為:
class TimeSheet { public double Charge(IBillable emp, int days) { int baseCharge = emp.GetRate() * days; if (emp.HasSpecialSkill()) { return baseCharge * 1.05; } return baseCharge; } }
到目前為止,我們只不過是在文檔化方面有一點收穫。但就這一個函數而言,這樣的收穫並沒有太大的價值;但如有若幹個類都使用IBillable介面,它就會很有用。如果我還想計算電腦租金,巨大的收穫就顯露出來了;要想計算客戶租用電腦的費用,只需讓Computer類實現IBillable介面,然後就可以把租用電腦的時間也填到時間表上了。
小結
這個重構策略也是一個常見的運用,很多設計模式也會在其中運用此思想。
9Collapse Hierarchy(摺疊繼承體系)
概要
基類和子類之間無太大區別。將它們合為一體。
動機
如果你曾經編寫過繼承體系,就會知道,繼承體系很容易變得過分複雜。所謂重構繼承體系,往往是將函數和欄位在體系中上下移到。完成這些動作後,你很可能發現某個子類並未帶來該有的價值,因此需要把基類和子類合併起來。
範例
如下代碼所示,StudentWebSite子類除了有一個屬性用來說明網站是否是活動的外沒有別的責任,在這種情形下我們意識到IsActive屬性可以應用到所有的網站,所以我們可以將IsActive屬性上移到基類中,並去掉StudentWebSite類。
public class Website { public string Title { get; set; } public string Description { get; set; } public IEnumerable<Webpage> Pages { get; set; } } public class StudentWebsite : Website { public bool IsActive { get; set; } } public class Webpage { }
重構後的代碼如下:
public class Website { public string Title { get; set; } public string Description { get; set; } public IEnumerable<Webpage> Pages { get; set; } public bool IsActive { get; set; } } public class Webpage { }
小結
這項重構和前幾篇最主要論述了子類和父類的繼承關係以及如何判斷什麼時候需要使用繼承,一般我們都能處理好這些關係,所以相對比較簡單。
To Be Continued……