代碼復用是絕大多數程序員所期望的
也是OO的目標之一
總結我多年的編碼經驗
為了使代碼能夠最大程度上復用
應該特別注意以下幾個方面
對接口編程
對接口編程
是面向對象設計(OOD)的第一個基本原則
它的含義是
使用接口和同類型的組件通訊
即
對於所有完成相同功能的組件
應該抽象出一個接口
它們都實現該接口
具體到JAVA中
可以是接口(interface)
或者是抽象類(abstract class)
所有完成相同功能的組件都實現該接口
或者從該抽象類繼承
我們的客戶代碼只應該和該接口通訊
這樣
當我們需要用其它組件完成任務時
只需要替換該接口的實現
而我們代碼的其它部分不需要改變!
當現有的組件不能滿足要求時
我們可以創建新的組件
實現該接口
或者
直接對現有的組件進行擴展
由子類去完成擴展的功能
優先使用對象組合
而不是類繼承
優先使用對象組合
而不是類繼承
是面向對象設計的第二個原則
並不是說繼承不重要
而是因為每個學習OOP的人都知道OO的基本特性之一就是繼承
以至於繼承已經被濫用了
而對象組合技術往往被忽視了
下面分析繼承和組合的優缺點
類繼承允許你根據其他類的實現來定義一個類的實現
這種通過生成子類的復用通常被稱為白箱復用(white
box reuse)
術語
白箱
是相對可視性而言
在繼承方式中
父類的內部細節對子類可見
對象組合是類繼承之外的另一種復用選擇
新的更復雜的功能可以通過組合對象來獲得
對象組合要求對象具有良好定義的接口
這種復用風格被稱為黑箱復用(black
box reuse)
因為被組合的對象的內部細節是不可見的
對象只以
黑箱
的形式出現
繼承和組合各有優缺點
類繼承是在編譯時刻靜態定義的
且可直接使用
類繼承可以較方便地改變父類的實現
但是類繼承也有一些不足之處
首先
因為繼承在編譯時刻就定義了
所以無法在運行時刻改變從父類繼承的實現
更糟的是
父類通常至少定義了子類的部分行為
父類的任何改變都可能影響子類的行為
如果繼承下來的實現不適合解決新的問題
則父類必須重寫或被其他更適合的類替換
這種依賴關系限制了靈活性並最終限制了復用性
對象組合是通過獲得對其他對象的引用而在運行時刻動態定義的
由於組合要求對象具有良好定義的接口
而且
對象只能通過接口訪問
所以我們並不破壞封裝性
只要類型一致
運行時刻還可以用一個對象來替代另一個對象
更進一步
因為對象的實現是基於接口寫的
所以實現上存在較少的依賴關系
優先使用對象組合有助於你保持每個類被封裝
並且只集中完成單個任務
這樣類和類繼承層次會保持較小規模
並且不太可能增長為不可控制的龐然大物(這正是濫用繼承的後果)
另一方面
基於對象組合的設計會有更多的對象(但只有較少的類)
且系統的行為將依賴於對象間的關系而不是被定義在某個類中
注意
理想情況下
我們不用為獲得復用而去創建新的組件
只需要使用對象組合技術
通過組裝已有的組件就能獲得需要的功能
但是事實很少如此
因為可用的組件集合並不豐富
使用繼承的復用使得創建新的組件要比組裝已有的組件來得容易
這樣
繼承和對象組合常一起使用
然而
正如前面所說
千萬不要濫用繼承而忽視了對象組合技術
相關的設計模式有
Bridge
Composite
Decorator
Observer
Strategy等
下面的例子演示了這個規則
它的前提是
我們對同一個數據結構
需要以任意的格式輸出
第一個例子
我們使用基於繼承的框架
可以看到
它很難維護和擴展
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();
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26568.html