標籤:
類繼承是在編譯時間刻靜態定義的,且可直接使用,類繼承可以較方便地改變父類的實現。但是類繼承也有一些不足之處。首先,因為繼承在編譯時間刻就定義了,所以無法在運行時刻改變從父類繼承的實現。更糟的是,父類通常至少定義了子類的部分行為,父類的任何改變都可能影響子類的行為。如果繼承下來的實現不適合解決新的問題,則父類必須重寫或被其他更適合的類替換。這種依賴關係限制了靈活性並最終限制了複用性。 第一個例子,我們使用基於繼承的架構,可以看到,它很難維護和擴充。 abstract class AbstractExampleDocument { // skip some code ... public void output(Example structure) { if( null != structure ) { this.format( structure ); } } protected void format(Example structure); } 第二個例子,我們使用基於對象組合技術的架構,每個對象的任務都清楚的分離開來,我們可以替換、擴充格式類,而不用考慮其它的任何事情。 class DefaultExampleDocument { // skip some code ... public void output(Example structure) { ExampleFormatter formatter = (ExampleFormatter) manager.lookup(Roles.FORMATTER); if( null != structure ) { formatter.format(structure); } } } 這裡,用到了類似於"抽象工廠"的組件建立模式,它將組件的建立過程交給manager來完成;ExampleFormatter是所有格式的抽象父類; 將可變的部分和不可變的部分分離 "將可變的部分和不可變的部分分離"是物件導向設計的第三個原則。如果使用繼承的複用技術,我們可以在抽象基類中定義好不可變的部分,而由其子類去具體實現可變的部分,不可變的部分不需要重複定義,而且便於維護。如果使用對象組合的複用技術,我們可以定義好不可變的部分,而可變的部分可以由不同的組件實現,根據需要,在運行時動態配置。這樣,我們就有更多的時間關注可變的部分。 對於對象組合技術而言,每個組件只完成相對較小的功能,相互之間耦合比較鬆散,複用率較高,通過組合,就能獲得新的功能。 減少方法的長度 通常,我們的方法應該只有盡量少的幾行,太長的方法會難以理解,而且,如果方法太長,則應該重新設計。對此,可以總結為以下原則: ☆ 三十秒原則:如果另一個程式員無法在三十秒之內瞭解你的函數做了什麼(What),如何做(How)以及為什麼要這樣做(Why),那就說明你的代碼是難以維護的,必須得到提高; ☆ 一屏原則:如果一個函數的代碼長度超過一個螢幕,那麼或許這個函數太長了,應該拆分成更小的子函數; ☆ 一行代碼盡量簡短,並且保證一行代碼只做一件事:那種看似技巧性的冗長代碼只會增加代碼維護的難度。 消除case / if語句 要盡量避免在代碼中出現判斷語句,來測試一個對象是否某個特定類的執行個體。通常,如果你需要這麼做,那麼,重新設計可能會有所協助。我在工作中遇到這樣的一個問題:我們在使用JAVA做XML解析時,對每個標籤映射了一個JAVA類,採用SAX(簡單的XML介面API:Simple API for XML)模型。結果,代碼中反覆出現了大量的判斷語句,來測試當前的標籤類型。為此,我們重新設計了DTD(文件類型定義:Document Type Definition),為每個標籤增加了一個固定的屬性:classname,而且重新設計了每個標籤映射的JAVA類的介面,統一了每個對象的操作: addElement(Element aElement); //增加子項目 addAttribute(String attName, String attValue); //增加屬性; 則徹底消除了所有的測試當前的標籤類型的判斷語句。每個對象通過 Class.forName(aElement.attributes.getAttribute("classname")).newInstence(); 動態建立, 減少參數個數 有大量參數需要傳遞的方法,通常很難閱讀。我們可以將所有參數封裝到一個對象中來完成對象的傳遞,這也有利於錯誤跟蹤。 許多程式員因為,太多層的對象封裝對系統效率有影響。是的,但是,和它帶來的好處相比,我們寧願做封裝。畢竟,"封裝"也是OO的基本特性之一,而且,"每個對象完成盡量少(而且簡單)的功能",也是OO的一個基本原則。 類層次的最高層應該是抽象類別 在許多情況下,提供一個抽象基類有利做特性化擴充。由於在抽象基類中,大部分的功能和行為已經定義好,使我們更容易理解介面設計者的意圖是什麼。 由於JAVA不允許"多繼承",從一個抽象基類繼承,就無法再從其它基類繼承了。所以,提供一個抽象介面(interface)是個好主意,一個類可以實現多個介面,從而類比實現了"多繼承",為類的設計提供了更大的靈活性。 盡量減少對變數的直接存取 對資料的封裝原則應該正常化,不要把一個類的屬性暴露給其它類,而是應該通過存取方法去保護他們,這有利於避免產生波紋效應。如果某個屬性的名字改變,你只需要修改它的存取方法,而不是修改所有相關的代碼。 子類應該特性化,完成特殊功能 如果一個子類只是使一個組件變成組件管理器,而不是實現介面功能,或者,重載某個功能,那麼,就應該使用一個外部的容器類,而不是建立一個子類。 建議:類階層圖,不要太深; 例如:下面的介面定義了組件的功能:發送訊息;類Transceiver實現了該介面;而其子類Pool只是管理多個Transceiver對象,而沒有提供自己的介面實現。建議使用組合方式,而不是繼承! public interface ITransceiver{ public abstract send(String msg); } public class Transceiver implements ITransceiver { public send(String msg){ System.out.println(msg); } } //使用繼承方式的實現 public class Pool extends Transceiver{ private List pool = new Vector(); public void add(Transceiver aTransceiver){ pool.add(aTransceiver); } public Transceiver get(int index){ pool.get(index); } } //使用組合方式的實現 public class Pool { private List pool = new Vector(); public void add(Transceiver aTransceiver){ pool.add(aTransceiver); } public Transceiver get(int index){ pool.get(index); } } 拆分過大的類 如果一個類有太多的方法(超過50個),那麼它可能要做的工作太多,我們應該試著將它的功能拆分到不同的類中,類似於規則四。 作用截然不同的對象應該拆分 在構建的過程中,你有時會遇到這樣的問題:對同樣的資料,有不同的視圖。某些屬性描述的是資料結構怎樣產生,而某些屬性描述的是資料結構本身。最好將這兩個視圖拆分到不同的類中,從類名上就可以區分出不同視圖的作用。 類的域、方法也應該有同樣的考慮! 盡量減少對參數的隱含傳遞 兩個方法處理類內部同一個資料(域),並不意味著它們就是對該資料(域)做處理。許多時候,該資料(域)應該作為方法的參輸入數,而不是直接存取,在工具類的設計中尤其應該注意。例如: public class Test{ private List pool = new Vector(); public void testAdd(String str){ pool.add(str); } public Object testGet(int index){ pool.get(index); } } 兩個方法都對List對象pool做了操作,但是,實際上,我們可能只是想對List介面的不同實現Vector、ArrayList等做存取測試。所以,代碼應該這樣寫: public class Test{ private List pool = new Vector(); public void testAdd(List pool, String str){ pool.add(str); } public Object testGet(List pool, int index){ pool.get(index); } }
C++中繼承會破壞封裝嗎?