top
Loading...
ASP.NET入門隨想之多態、接口與委托
曾幾何時,我們居住的陸地沉睡在海底,大陸也緊密的聯系在一起,千百年過去了七大洲的地殼板塊在緩緩移動,喜馬拉雅山在慢慢增高,世界在變,唯一不變的是變化。

■ 軟件開發的悖論 - 把變化變成計劃

在軟件開發活動的過程中,常被一個悖論所纏繞:不寫碼就搞不清該做什么;搞不清做什么又不知道該如何寫碼。

人的思維是很隨意的東西,不同的人,或同一個人的不同階段,對同一件事情的看法都會有差異,可謂是遠看成嶺側成峰,遠近高低各不同。隨意再加上變化,常常引起項目流產或工期大大拖延。

之所以會有軟件工程,是因為想盡量挽救我們那白白死去的腦細胞和消逝的人民幣。CIM/ISO官僚,每一個環節都要進行所謂科學管理與評估,質量差就返工,爭取每一種構造活動只進行一次。難度在于開發活動很難定量計算,所謂評估常就是大家一起到酒桌上拼拼,一些活動初期的錯誤在后續無限放大;商業驅動的RUP有些瞎子摸象,要求用最短的時間拼出愛因斯坦粗糙的第一張凳子交給用戶,得到確認后在此基礎上不斷循環改造,一直到用戶滿意的第N張為止,需要大量的用戶參與,可是用戶不是軟件工程師,要他們來把握復雜的活動計劃,難為他們。總之兩者都有缺陷,修正錯誤和迭代開發是必然,所以軟件架構設計伊始就要考慮適應需求變化,力將變化帶來的影響減少到最小。 OO開發范式大致為:劃分對象→抽象類→將類組織成為層次化結構(繼承和合成) →用類與實例進行設計和實現幾個階段。在這個過程,合理使用OO語言的一些特性將影響軟件對需求變化的適應力,其使用熟練度往往成為評價軟件開發人員的標桿。

■ 盡可能遠離switch - 多態與抽象類

OO思想的基礎在于把具備狀態和方法的對象看作系統組成的基本粒子,用粒子之間的相互作用(或通訊)來描述系統的行為。通常方法在在某個類的作用域中定義方法接口和實現,并用于修改類的狀態,如:泰坦尼克.移動(方向),這時方法是類的配角,為類服務,即類的方法。

類的方法可能會有多種形態,只要簽名(返回值和參數列表)不同,我們可以通過方法重載來達到概念(方法名)統一,如:泰坦尼克.移動(方向)與泰坦尼克.移動(方向,水流),這兩種狀況的實現不一樣,但作為類的使用者概念而言,都是泰坦尼克在移動。而在面向過程時代,方法的多態往往是由switch語句來負責處理。

結構化語言初行之時,非結構化語言被猛烈攻擊的焦點居然集中在一條語句--goto身上,說它破壞了程序的組織結構,仿佛它就是萬惡之源;與之類似的是,十幾年后OO語言風行之始,人們同樣找到一條語句大肆抨擊,稱它為僵化的毒藥,它就是switch語句。switch語句是典型的結構化編程思想,眾多模塊的公共屬性被映射到標志變量值域上的一點,簡單地排列在一塊,如下例:

switch (船類型){
case "商船"
商船的移動計算方法; Break;
case "戰船"
戰船的移動計算方法; Break;
}

上例中,switch語句的問題在于它謀殺了眾多船類移動計算方法之間的結構本質,缺乏對相似方法應有的抽象。后果是隨處可見它那臃腫的身影。一旦需求變動,我們就不得不四處搜尋,一一修正。

在這里移動方法實際上是船類系的公共方法。作為類系的使用者,他必然希望象重載一樣做到概念(方法名)統一,而通過方法的簽名來區分是商船還是戰船在移動,所以我們調整如下:

void 控制移動(船類 ship){
ship.移動;
……
}

戰船 俾斯麥 = new戰船();
商船 泰坦尼克 = new商船();
控制移動(俾斯麥);
控制移動(泰坦尼克);

在控制移動(泰坦尼克)方法中,我們用商船的移動方法替換了ship.移動方法,其實這就是《隨想六》提到的里氏替換原則,即子類型可以替換基型。類系方法的多態是由上傳(upcast)機制來實現,如圖7-1。與重載一樣,由OO語言自動完成。


上傳機制有一個很直觀的好處,如果船基類又臨時增加賽船子類, 我們在不修改已有代碼的前提下輕松完成對原有軟件的擴展,即"開放-封閉"原則(Open-Closed Principle)。如增加以下代碼并不影響控制移動方法:

賽船 麥哲倫 = new賽船();
控制移動(麥哲倫);

只所以功能擴展不影響控制移動方法,是因為控制移動方法只依賴于船類系的基類。如果把一個類系的公共方法集中起來,定義成抽象類(abstract),其它類只與抽象類發生依賴關系,只要抽象類的方法接口不變,其繼承子類的內部調整不影響外界的使用,最大程度達到外界與這個類系的架構(這時指依賴關系)穩定。作為一個抽象類,它具備兩個重要特征:一是方法只有接口定義沒有實現,二是子類(非抽象類繼承)必須實現抽象類的所有方法。抽象類不能實例化,也沒有必要實例化,就一空架子折騰啥呀。

■ 我是主角 - 接口與委托

OO思想一般強調類為主角,方法是類的配角,尤其抽象類系,基類與所有子類是強耦合關系,它要是倒下了,人心就散了,隊伍就難帶了。一旦抽象類需要變動(增刪改類系的公共方法),就對一個軟件架構的影響程度而言,不亞于俄羅斯對烏克蘭天然氣漲價給歐洲帶來的振憾,有可能帶來多米諾骨牌效應。 當方法不僅僅只是一個而是多個類系的公共行為的抽象時,我們就必須把配角方法升格為主角,讓它來支配相關類系,做法是將公共方法接口獨立出來,單獨定義成接口(interface),然后由相關類系一一實現,這時我們可以把其稱為方法的類。所有外界與其關系不再建立在抽象類而是建立在接口之上,如圖7-2。在定義接口的時候,注意不要讓其過度臃腫,一個類對另外一個類的依賴性應當是建立在最小接口上。復雜的接口可以通過接口的多重繼承或委托來分離。


之所以一步步地把方法從配角升格為主角,是因為我們希望將需求中不穩定的成分隔離出來,用相對穩定的成分作為框架來構造系統。而一般概念的生存周期要遠遠大于特殊概念,如:職位>總裁>方興東,所以要依賴于抽象而不要依賴于具體,即依賴倒置原則(Dependence Inversion Principle),前面談到的"開放-封閉"原則是其外延。

此外盡管方法從配角變為主角,其實現還是在該類的作用域中。有時方法更出格,委托類除了定義方法簽名和通知其開工消息外,對方法的實現完全失去控制,它甚至到上臺以后才知道今天的主角究竟是誰。這就是委托(delegate)機制。實質是類直接引用另一個類的方法,只要委托方法與受托方法接口一致,目的是徹底將方法的接口與實現分離。

class 商船{
//委托類
private int 動力;
private delegate int 阻力計算委托(); //定義委托 public void 移動(阻力計算委托 阻力){
int 作用力 = 動力 -阻力();
……
}
}
class Test{
//受托類
private int 風阻力計算(){……};
static void main(){
商船 泰坦尼克;
泰坦尼克.移動(new阻力計算委托(風阻力計算)) //委托實例
}
}

委托可以這么理解,上例中商船類為委托方張三,Test類為受托方李律師,張三(商船類)稱我要告王五(阻力計算委托),李律師胸脯一拍:沒問題,這事我按民事訴訟程序(風阻力計算方法)來辦。
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗