最後討論一下無法繼承多種類的裝配問題
該問題的原因在於Java不允許多層繼承
Java不能進行多層繼承
這一點大多經常作為其優點而不是缺點而被提出來
C++允許多層繼承
但結果卻致使程序變得非常復雜
而且還產生了難以理解的錯誤
多層繼承在裝配時會很復雜
多層繼承會在如下幾個方面導致混亂
第一是名稱的沖突
在繼承具有相同名稱的其他類時
不知道是哪個類的方法將會被調用(圖
)
這一點是在一個類繼承兩個已經繼承了某一個類的類時經常發生的問題
這種繼承稱為菱形繼承(圖
)
在分別改寫(override)最上層的類的方法後
就會引起被最下層的類所調用的問題
圖
●在需要繼承兩個具有相同名稱的方法的超級類時
其名稱就會發生沖突
這一點是經常被作為多層繼承的問題而指出來的地方
圖
●菱形繼承的一個例子
在同一個人具有社會人和學生兩種性質的時候
就必然會引起此類問題
第二
類的層次也會變得很復雜
如果只有單繼承
類的結構僅僅只會分支成樹狀
而如果是多層繼承
一下子就會變得很復雜
如果多次反復地進行多層繼承
掌握類的層次關系幾乎就將變得不可能
由於Java只允許單繼承
因此就不會引起這種問題
不過
用戶當然也希望通過將多個不同的類組合起來
生成新的類
為了解決這一問題
Java就提供了被稱為接口的多層繼承的功能
日本產業技術綜合研究所信息處理研究部的主任研究員一杉裕志說
Java只允許單繼承從編程語言的裝配的角度來講是一種很不錯的作法
甚至有人表示
Java利用接口來取代多層繼承給人的感覺非常不錯
此前Java之所以如此普及就是因為Java的語言標准采取了適當的折衷
(東京大學研究生院處理理工學系研究專業專門從事計算機科學的萩谷昌己教授)
接口不能繼承裝配
圖
●Java只允許繼承一個裝配
因此就必須完整地復制代碼
不過
基於接口的偽多層繼承存在致命的缺點
這就是指接口無法繼承裝配
Ruby語言的開發者
日本的松本幸弘指出
使用接口取代多層繼承是Java的高明之處
不過放棄裝配的繼承則令人非常痛心
繼承
個類的功能時
Java將會復制某一個類的源代碼
結果
相同的代碼就會分散在各種不同的位置
可維護性就會顯著降低(圖
)
將相同的處理過程集中到名為類的單位中來提高面向對象的優點沒有得到充分的利用
從市場銷售上來講
Java是一種取得了巨大成功的語言
但是從編程語言的裝配角度來看
我認為它是一個失敗的作品
(松本)
Ruby語言利用Mix
in實現裝配的繼承
實際上松本開發的Ruby語言也和Java一樣只允許單繼承
不過
Ruby則利用相當於Java接口的方法實現了裝配的繼承
這就是被稱為Mix
in的方法
Mix
in是指僅將程序中具有再利用性的功能部分集中到被稱為
模塊
的單位中
以供其他的類來使用
模塊與類一樣
其本身無法利用new運算符來生成
只有
include
即嵌入到其他的類中才能使用(圖)
LIST
就是使用了Mix
in方法的簡單Ruby代碼
定義了一個可以輸出
test
字符串的
名為TestModule的模塊
TestClass類嵌入了該模塊
這樣一來
TestClass類就可以像調用自身的方法一樣調用TestModule模塊中的方法
不過
Ruby的Mix
in所解決的只是由多層繼承所造成的繼承關系的復雜性
在多個模塊存在同名的方法時
就會產生與多層繼承相同的問題
Ruby優先處理由後面嵌入的方法(注
)
圖
●Ruby中的Mix
in功能
將集中了各種功能的模塊嵌入到類中來使用
LIST
●使用Ruby中Mix
in功能的代碼
TestClass類可以像使用自己的方法一樣使用TestModule模塊所裝配的方法
利用與類不同的單位進行程序再利用的MixJuice語言
目前一種利用與Ruby不同的方法來提高程序的再利用性的名為MixJuice的語言也在開發之中
這是一種基於Java的獨立語言
LIST
●MixJuice的代碼
程序以
module(模塊)
為單位進行描述
而類則由module來分割
為了程序的再利用
即便MixJuice也使用了與類不同的編程單位
這種單位與Ruby一樣被稱為
模塊
MixJuice語言的開發者
日本產業技術綜合研究所的一杉表示
在基於Java等語言中常見的類的程序設計中
通過多個類的協作實現某一種功能時
類就無法進行再利用
因此應該有一個與類不同的
可以再利用的編程單位
MixJuice語言中當然也有類
但是編程時所處理的並不是一個類的源代碼
而是以包含了各種可協調工作的類的
模塊
為單位來描述程序
重新利用模塊進行擴展時
描述的是模塊之間的
區別
LIST
就是實際的源代碼
程序是以
模塊
為單位來組織的
類則在模塊中使用
define
關鍵詞進行定義
如本例所示
通過追加模塊來描述與現有類的區別
把使用多個類而實現的功能集中到了一起
必然引起裝配缺陷
不過
這種方法必然會產生其他問題
就是說要使用MixJuice中的模塊來擴展類時
就會產生裝配缺陷
圖
就是一個具體的例子
模塊m
和m
均繼承了模塊m
m
定義了在m中定義的類S的派生類B
而m
則向S類追加了方法
如果是普通的方法
就不會產生問題
但是這裡卻定義了一個利用派生類來強制裝配的抽象方法
實際上
它的派生類A添寫了裝配
如要同時使用這兩個模塊
連接時就會產生錯誤
因為類B沒有裝配由m
追加到類S中的方法m()
為了解決這個問題
無論是誰擁有多麼豐富的知識都必須對方法進行裝配
這種問題是因模塊具有方法追加和子類(派生類)追加等
種擴展的方向性而引起的(圖
)
圖
●裝配缺陷的例子
由m
追加到類S中的抽象方法的存在與m
沒有任何關系
如果同時使用這
個模塊時就會產生編譯錯誤
圖
●存在二個以上的擴展方向性時就會產生裝配缺陷
拿MixJuice來說
就是子類和方法的擴展性
提出了新的分割單位的
面向側面編程(Aspect
Oriented programming)
像MixJuice語言那樣
利用橫跨多個對象的單位來把握系統的觀點稱為
關注分隔(Separation of Concerns
SoC)
類的相互作用也屬於關注的一種
這種觀點並不是僅僅單純以對象為單位
還要由其他側面來分割系統
最近這種觀點已經開始受到越來越廣泛的關注
基於該觀點的代表性編程范式(paradigm)就是面向側面編程
它就是以
關注
為分隔單位的
如果存在多個與類相同的關注
就通過將這些關注組合起來
實現一種功能
這種功能就是側面(Aspect)
圖
就是具體的例子
把移動圖形來刷新畫面的處理過程定義為一個側面
圖形是由點和線組成的
每一個點和線中均存在移動的方法和用於設置位置的方法
這些動作相互協作
就可實現稱為畫面刷新的側面
LIST
就是使用基於Java的面向側面語言
AspectJ
來描述側面的源代碼
在一個被稱為移動圖形的程序段中定義了多個類的多個方法
圖形的移動結束後
最後執行重新刷新畫面的處理過程
圖
●利用
關注
分割
類
的圖示
點和線以及他們各自所具有的定位和移動的方法被劃分成了相同的關注
該圖摘自面向側面編程的倡導者Gregor Kiczales在面向側面編程技術研討會上發表的演講資料
LIST
●利用AspectJ描述側面的源代碼
將用於實現圖像移動的各種類中的方法歸納為
move
move
結束以後
執行由
after
描述的畫面刷新
摘自Gregor Kiczales在面向側面編程技術研討會上發表的演講資料
與面向對象並不矛盾
面向側面編程在對象以外導入了分割系統的單位
提高了程序的再利用性和擴展性
如果只有對象單位有分割的軸
也許確實就會存在一定的限制吧
不過
面向對象本身就是公認難度很高的編程范式
盡管如此面向對象語言能夠普及到如此程序恐怕是因為
只有對象單位有分割的軸
吧
正因為軸只有一個好歹才能夠理解
如果還要加上被稱為側面的軸
也許有很多程序員就會產生混亂
另外
面向側面則是自由度很高的編程范式
正是因為自由度高
程序員才會感到苦惱
因為他們不知道應該如何分割或組織程序
也就是說
在某種程度上縮小自由度
並給程序員提供編程指南也可以說是編程語言的任務
面向側面是一種擴展了面向對象的編程范式
如果認為面向對象的自由度合適
就可以繼續使用它
就連面向側面的倡導者加拿大英屬哥倫比亞大學的Gregor Kiczales本人也表示
沒有必要非要去勉強地使用側面來編程
程序員要根據自己的開發風格
在追求一種易用性和擴展性適當平衡的同時
使用這種編程范式
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19661.html