熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

代碼復用的規則

2013-11-23 19:16:41  來源: Java核心技術 

  代碼復用是絕大多數程序員所期望的也是OO的目標之一總結我多年的編碼經驗為了使代碼能夠最大程度上復用應該特別注意以下幾個方面
   對接口編程
  對接口編程是面向對象設計(OOD)的第一個基本原則它的含義是使用接口和同類型的組件通訊對於所有完成相同功能的組件應該抽象出一個接口它們都實現該接口具體到JAVA中可以是接口(interface)或者是抽象類(abstract class)所有完成相同功能的組件都實現該接口或者從該抽象類繼承我們的客戶代碼只應該和該接口通訊這樣當我們需要用其它組件完成任務時只需要替換該接口的實現而我們代碼的其它部分不需要改變!
  
  當現有的組件不能滿足要求時我們可以創建新的組件實現該接口或者直接對現有的組件進行擴展由子類去完成擴展的功能
  
   優先使用對象組合而不是類繼承
  優先使用對象組合而不是類繼承是面向對象設計的第二個原則並不是說繼承不重要而是因為每個學習OOP的人都知道OO的基本特性之一就是繼承以至於繼承已經被濫用了而對象組合技術往往被忽視了下面分析繼承和組合的優缺點
  
  類繼承允許你根據其他類的實現來定義一個類的實現這種通過生成子類的復用通常被稱為白箱復用(whitebox reuse)術語白箱是相對可視性而言在繼承方式中父類的內部細節對子類可見
  
  對象組合是類繼承之外的另一種復用選擇新的更復雜的功能可以通過組合對象來獲得對象組合要求對象具有良好定義的接口這種復用風格被稱為黑箱復用(blackbox reuse)因為被組合的對象的內部細節是不可見的對象只以黑箱的形式出現
  
  繼承和組合各有優缺點類繼承是在編譯時刻靜態定義的且可直接使用類繼承可以較方便地改變父類的實現但是類繼承也有一些不足之處首先因為繼承在編譯時刻就定義了所以無法在運行時刻改變從父類繼承的實現更糟的是父類通常至少定義了子類的部分行為父類的任何改變都可能影響子類的行為如果繼承下來的實現不適合解決新的問題則父類必須重寫或被其他更適合的類替換這種依賴關系限制了靈活性並最終限制了復用性
  
  對象組合是通過獲得對其他對象的引用而在運行時刻動態定義的由於組合要求對象具有良好定義的接口而且對象只能通過接口訪問所以我們並不破壞封裝性只要類型一致運行時刻還可以用一個對象來替代另一個對象更進一步因為對象的實現是基於接口寫的所以實現上存在較少的依賴關系
  
  優先使用對象組合有助於你保持每個類被封裝並且只集中完成單個任務這樣類和類繼承層次會保持較小規模並且不太可能增長為不可控制的龐然大物(這正是濫用繼承的後果)另一方面基於對象組合的設計會有更多的對象(但只有較少的類)且系統的行為將依賴於對象間的關系而不是被定義在某個類中
  
  注意理想情況下我們不用為獲得復用而去創建新的組件只需要使用對象組合技術通過組裝已有的組件就能獲得需要的功能但是事實很少如此因為可用的組件集合並不豐富使用繼承的復用使得創建新的組件要比組裝已有的組件來得容易這樣繼承和對象組合常一起使用然而正如前面所說千萬不要濫用繼承而忽視了對象組合技術
  
  相關的設計模式有BridgeCompositeDecoratorObserverStrategy等
  
  下面的例子演示了這個規則它的前提是我們對同一個數據結構需要以任意的格式輸出
  
  第一個例子我們使用基於繼承的框架可以看到它很難維護和擴展
  abstract class AbstractExampleDocument
  {
  // skip some code
  public void output(Example structure)
  {
  if( null != structure )
  {
  thisformat( structure );
  }
  }
  protected void format(Example structure);
  }
  
  
  
  
  第二個例子我們使用基於對象組合技術的框架每個對象的任務都清楚的分離開來我們可以替換擴展格式類而不用考慮其它的任何事情
  class DefaultExampleDocument
  {
   // skip some code
   public void output(Example structure)
   {
   ExampleFormatter formatter =
   (ExampleFormatter) managerlookup(RolesFORMATTER);
   if( null != structure )
   {
   formatterformat(structure);
   }
   }
  }
  
  
  這裡用到了類似於抽象工廠的組件創建模式它將組件的創建過程交給manager來完成ExampleFormatter是所有格式的抽象父類
  
   將可變的部分和不可變的部分分離
  將可變的部分和不可變的部分分離是面向對象設計的第三個原則如果使用繼承的復用技術我們可以在抽象基類中定義好不可變的部分而由其子類去具體實現可變的部分不可變的部分不需要重復定義而且便於維護如果使用對象組合的復用技術我們可以定義好不可變的部分而可變的部分可以由不同的組件實現根據需要在運行時動態配置這樣我們就有更多的時間關注可變的部分
  
  對於對象組合技術而言每個組件只完成相對較小的功能相互之間耦合比較松散復用率較高通過組合就能獲得新的功能
  
   減少方法的長度
  通常我們的方法應該只有盡量少的幾行太長的方法會難以理解而且如果方法太長則應該重新設計對此可以總結為以下原則
  
  三十秒原則
  如果另一個程序員無法在三十秒之內了解你的函數做了什麼(What)如何做(How)以及為什麼要這樣做(Why)那就說明你的代碼是難以維護的必須得到提高
  一屏原則
  如果一個函數的代碼長度超過一個屏幕那麼或許這個函數太長了應該拆分成更小的子函數
  一行代碼盡量簡短並且保證一行代碼只做一件事
  
  那種看似技巧性的冗長代碼只會增加代碼維護的難度
  
   消除case / if語句
  要盡量避免在代碼中出現判斷語句來測試一個對象是否某個特定類的實例通常如果你需要這麼做那麼重新設計可能會有所幫助我在工作中遇到這樣的一個問題我們在使用JAVA做XML解析時對每個標簽映射了一個JAVA類采用SAX(簡單的XML接口APISimple API for XML)模型結果代碼中反復出現了大量的判斷語句來測試當前的標簽類型為此我們重新設計了DTD(文檔類型定義Document Type Definition)為每個標簽增加了一個固定的屬性classname而且重新設計了每個標簽映射的JAVA類的接口統一了每個對象的操作 addElement(Element aElement); //增加子元素
  addAttribute(String attName String attValue); //增加屬性
  
  則徹底消除了所有的測試當前的標簽類型的判斷語句每個對象通過 ClassforName(aElementattributesgetAttribute(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){
   Systemoutprintln(msg);
   }
  }
  
  //使用繼承方式的實現
  public class Pool extends Transceiver{
   private List pool = new Vector();
   public void add(Transceiver aTransceiver){
   pooladd(aTransceiver);
  }
  public Transceiver get(int index){
   poolget(index);
  }
  }
  
  //使用組合方式的實現
  public class Pool {
   private List pool = new Vector();
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26568.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.