程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA編程入門知識 >> 最大化JAVA代碼的可重用性

最大化JAVA代碼的可重用性

編輯:JAVA編程入門知識


   ——克服傳統OO方法在重用方面的缺陷
  
   摘要:不要放棄編寫可重用代碼的努力!本文介紹了三種對現有代碼進行修改以提高其可重用性的方法。
  
   在程序員中似乎存在著一種日益普遍的觀點,認為重用只是一個神話。或許是傳統的面向對象編程方法中所存在的不足增加了重用的困難。本文介紹了從另外一種不同的途徑使重用成為可能的三個步驟。
  
   第一步:將功能實現從類實例的方法中移出
  
   由於缺乏精確性,類繼續不是非常理想的代碼重用機制。換句話說,假如不繼續一個類的數據成員和其他的方法,那麼你就無法重用這個類的某個單獨的方法。這些額外的不必要的負擔使方法重用的代碼變得復雜。派生類對其父類的依靠性也以入了額外的復雜性:對父類的改動會對子類造成影響;當修改任意一個類的時候,我們很難記得清哪個方法被覆蓋,哪個沒有;而且被覆蓋的方法是否會調用父類中相應的方法並不非常清楚地顯現。
  
   任何執行單一概念任務的方法應該能夠成為代碼用的首選而獨立存在。為了達到這個目標,我們必須會到過程化的編程模式,將代碼從類實例的方法中移出,形成具有全局可見性的過程。為了提高這種過程的可重用性,過程代碼應該象靜態的通用方法一樣編寫:每個過程只能使用自己的輸入參數,只能調用其他全局性的過程完成其工作,不能使用任何非本地的變量。這種對外部依靠的簡化降低了過程使用的復雜性,也增加了在其他地方使用此過程的可能性。當然,由於其結構通常會變得更為清楚,即使拋開重用的目的不談我們也可以從這種代碼的組織方式中受益。
  
   在Java中,方法不能脫離類而單獨存在。因此,你可以對相關的過程進行組織並使它們成為一個獨立的類中的公共靜態方法。例如,對於如下所示的一個類:
  
   class Polygon{
  
   …
  
   public int getPerimeter(){…}
  
   public boolean isConvex(){…}
  
   public Boolean containsPoint(Point p){…}
  
   …
  
   }
  
   可以將它改寫成下面的形式:
  
   class Polygon {
  
   …
  
   public int getPerimeter() { return pPolygon.computePerimeter(this);}
  
   public boolean isConvex() { return pPolygon.isConvex(this);}
  
   public boolean containsPoint() { return pPolygon.containsPoint(this, p);}
  
   …
  
   }
  
   在此處,nPolygon應該是這個樣子:
  
   class pPolygon {
  
   static public int computePerimeter(Polygon polygon) {...}
  
   static public boolean isConvex(Polygon polygon) {...}
  
   static public boolean containsPoint(Polygon polygon, Point p) {...}
  
   }
  
   從類的名字pPolygon可以看出,該類所封裝的過程主要與Polygon類型的對象有關。名字前面的p表示該類的唯一目的是組織公共靜態過程。在Java中,類的名字以小寫字母開頭不是一種標准的做法,但象pPloygon這樣的類事實上並不執行普通類的功能。也就是說,它並不代表著一類對象,它只是語言本身所需要的用於代碼組織的實體。
  
    在上面這個例子中,改動代碼的總體影響是使得客戶代碼不必為了重用其功能而從Polygon繼續。Polygon類的功能現在已經由pPolygon類以過程為單位提供。客戶代碼只使用自己需要的代碼,無需關心自身並不需要的功能。
  
   這並不意味著在這種新型的過程化編程模式中,類不服務於更有用的目的。恰恰相反,類執行組織和封裝對象數據成員的必要工作。而且它們通過多重接口實現多態性的能力也為代碼重用提供了顯著的支持,這將在下一個步驟中討論。然而,由於將功能實現包含在實例方法中無法實現理想的代碼重用,所以通過類繼續實現代碼重用和多態性支持也不應成為最佳的技術選擇。
  
   在一本被廣為閱讀的書《Design Patterns》中曾簡要地提及一種略有不同的技術。策略模式(Strategy Pattern)提倡將相關算法的每個成員封裝在一個通用的接口下,以便於客戶端代碼可交換地使用其算法。由於一個算法通常被作為一個或幾個獨立的過程進行編碼,這種封裝更注重執行單獨任務的過程的重用,而不是執行多種任務的、包含代碼和數據的對象的重用。這一步驟體現了相同的基本思想。
  
   然而,將一個算法封裝在一個接口下意味著將算法作為實現接口的對象進行編碼。這意味著我們仍然依靠於一個與所包裝的對象的數據和其他方法相耦合的過程,這樣便會使其復用變得復雜。此外還存在這樣一個問題,每次需要使用這個算法的時候都必須實例化這些對象,這便會降低程序的性能。值得慶幸的是,設計模式提供了針對這兩個問題的解決方法。可以在對策略對象進行編碼時應用享元模式(Flyweight Pattern,譯者注:還存在一種譯法為輕量模式),這樣每個對象只會存在一個被共知共享的實例(這針對程序性能的問題),而且每個共享對象在訪問間隔中並不維持狀態(於是對象將沒有數據成員,這針對大多數的耦合問題)。由此產生的享元--策略模式非常類似於在這一步驟中所提到的將功能實現封裝在全局可見的、無狀態的過程中的技術。(譯者注:以上這兩段文字讀起來可能有些晦澀難解,建議有愛好的讀者參閱文中所提到《設計模式》一書,Erich Gamma等著、李英軍等譯、機械工業出版社出版。)
  
   第二步:將非原始的輸入參數類型改為接口類型
  
   在面向對象編程中,代碼重用的真正基礎在於通過接口參數類型利用多態性,而不是通過類繼續,正如Allen Holub在 “Build User Interfaces for Object-Oriented System, Part 2”中所述:
  
   “……你應該通過對接口而不是類編程實現重用。假如一個方法的所有參數都是某個已知接口的引用,這個接口由一些你所不知道的類實現,那麼這個方法就能夠操作這樣一些對象:當編寫方法的代碼時,這些對象的類甚至還不存在。從技術上講,可重用的是方法,而不是傳遞給方法的對象。”
  
   將Holub所講的方法應用於第一步所得到的結果,只要某塊功能代碼能夠作為一個全局可見的過程而獨立存在,你就可以將其每個類類型(class-type)的輸入參數改為一個接口類型,這樣便能進一步提高其重用的潛力。那麼,實現此接口類型的任何類的對象都可以作為參數使用,而不僅僅局限於原始類。由此,這個過程對可能存在的大量的對象類型都成為可用的。
  
   例如,有這樣一個全局可見的靜態過程
  
   static public boolean contains(Rectangle rect, int x, int y) {…}
  
   這個方法用於檢查給定的矩形是否包含某個給定的點。在這個例子中,rect參數的類型可以從Rectangle類改變為接口類型,如下所示:
  
   static public boolean contains(Rectangular rect, int x, int y){…}
  
   Rectangular可以是下面形式的接口:
  
   public interface Rectangular{
  
          Rectangle getBounds();
  
   }
  
   現在,所有可以被描述為矩形的類(也就意味著實現了Rectangular接口)的對象都可以作為傳遞給pRectangular.contains()的rect參數。通過放寬所傳遞的參數類型的限制,我們使方法具有更好的可重用性。
  
   不過,在上面這個例子中,Rectangular接口的getBounds方法返回一個Rectangle類型,你可能會懷疑使用這個接口是否具有真正的價值;換句話說,假如我們知道傳入過程的對象會在被調用時返回一個Rectangle,為什麼不直接傳入Rectangle取代接口類型呢?不這樣做的最重要原因與集合有關,假設有這樣一個方法:
  
   static public boolean areAnyOverlapping(Collection rects) {…}
  
   這個方法的目的在於檢查給定集合中的任意矩形對象是否存在重疊。那麼,在方法內部遍歷集合中的每個對象時,假如無法將對象造型(cast)成如Rectangular這樣的接口類型,那麼將如何能夠訪問對象的矩形區域呢?唯一的選擇是將對象造型成為其特定的類型(我們直到它有一個能夠返回rectangle的方法),這意味著方法必須事先知道其所要操作的是什麼類型。這恰恰是這一步驟力圖首先要避免的問題!
  
   第三步:選擇低耦合的輸入參數接口類型
  
   完成第二步之後,應該選擇什麼樣的接口類型來取代給定的類型呢?答案是能夠通過參數完全描述過程的需求,同時又具有最少的額外負擔的接口類型。參數對象所要實現的接口越簡單,其他特定類實現此接口的機會就越大——由此,其對象可以作為參數使用的類也就越多。通過下面的例子可以很輕易地看到這點:
  
   static public boolean areOverlapping(Window window1, Window window2) {...}
  
   這個方法用於檢查兩個窗口(假定是矩形窗口)是否重疊,假如這個方法只要求從參數獲得兩個窗口的矩形坐標,那麼簡化參數的類型使其能反映這個事實是一種更好的選擇:
  
   static public boolean areOverlapping(Rectangular rect1, Rectangular rect2) {...}
  
   以上的代碼假設先前的Window類型的對象同樣可以實現Rectangular接口。現在對於所有的矩形對象,都可以重用第一個方法所包含的功能了。
  
   你可能多次體驗到當一個接口能夠完全確定需要通過參數獲哪那些內容時,會存在太多不必要的方法。在這種情況下,應該在全局命名空間中定義一個新的公共接口以供其他可能面臨同一困境的方法重用。
  
   你可能還會不止一次地發現,在確定需要通過單一過程的一個參數獲取哪些內容時,最好創建一個單獨的接口。你應該只為這個參數使用此接口。這通常會在你希望如同C語言中的函數指針一樣使用參數的情況下出現。例如下面的過程:
  
   static public void sort(List list, SortComparison comp) {...}
  
   此過程使用參數所提供的比較對象comp,通過比較給定列表中的所有對象而對其進行排序,sort對comp的全部要求是調用一個單獨的方法進行比較。因此,SortComparison應該是只帶有一個方法的接口:
  
   public interface SortComparison {
  
   boolean comesBefore(Object a, Object b);
  
   }
  
   這個接口的唯一目的是為sort提供一個與其完成任務所需功能相聯系的鉤子(hook),因此SortComparison無法在其他地方重用。
  
   結束語
  
   以上所述的三個步驟用於現有的、按照相對傳統的面向對象方法所編寫的代碼。這些步驟與面向對象編程技術結合就形成了一種可以運用於今後代碼編寫中的新方法,它可以提高代碼的可重用性和內聚性,同時降低了耦合度及復雜性。
  
   很顯然,這些步驟無法運用於那些在本質上就不適合於重用的代碼。這類代碼通常出現在應用程序的表示層(presentation layer)。例如程序中用於創建用戶界面的代碼,以及將輸入事件與完成實際工作的過程相聯系的控制代碼,都是屬於那種其功能在不同的程序中差別很大的代碼,這種代碼的重用幾乎是不可能的。


 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved