當兩個程式碼元素之間存在相依性時,一旦被依賴的那個元素有了變化,就有機會造成另一個元素也受到波及,因而必須跟著變化。所以你可以想像,修改某個被許多其他的程式碼元素高度依賴的程式碼元素時,可能會影響到的層面究竟會有多廣泛。這是我們之所以要控制程式碼相依性的主要原因之一。
了解高相依性造成的影響
所謂程式碼元素之間的相依性,其實還是可以在不同的粒度(Granularity)上檢視。例如程式庫和程式庫之間的相依性、套件(Package)和套件之間的相依性、類別和類別之間的相依性……等。
但無論放在那一種粒度的層級上看,高相依性都不會是一件好事。例如大名鼎鼎的Jakarta Apache Project,其中有許多程式庫都和同一專案下的其他程式庫存在相依性,這意謂著採用其中一者,也得同時引入其他,這種高度耦合的情況,讓使用這些專案的開發者,被迫面對複雜的組態管理問題。
對一般程式設計者來說,多半比較關心套件之間的相依性,以及類別之間的相依性,尤其是後者,更是許多設計技巧與方法關注的重心。
舉例來說,當類別A相依於類別B時,意指當我們改變B的介面時,我們可能得修改A。也就是說,所謂的相依性,其實是對另一類別介面的依賴。從這個定義來判斷,下述的情況都是A相依於B的例子:一、A存取B的值,二、A呼叫B的函式,三、A外貌式(Signature)中的回傳型別或引數列表中含有B。
如果你曾使用UML設計建模,那麼對其中的相依性關係(Dependency Relationship)肯定不會陌生。不過,就「相依性」本身來看,我們在評估兩個類別之間是否存在相依性時,其中的「相依性」其涵蓋範圍,就不僅僅局限在UML中所提及的相依性關係,事實上它包含了更多的關係。
舉例來說,若子類別A繼承自父類別B,那麼當父類別B的介面有所變更時,子類別A難道沒有可能需要修改嗎?當然需要!類別A與類別B之間存在相依性,而這相依性則是基於繼承關係而來的。
分析靜態程式碼,呈現類別之間的相依情況
在明白我們想要探討的相依性後,讀者不妨暗自想想,自己在設計的時候,是否曾經將設計的相依性高低納入考量呢?是否會在設計時,試著降低類別之間的相依性呢?
有許多工具可以協助你分析系統中各類別之間的相依性。例如相依圖(Dependency Graph)。
|
Façade模式是為了消除客戶端類別與許多位於同一子系統中類別的相依性而設計。 資料來源:《Design Patterns》 |
所謂的相依圖,即是將系統中存在相依性的類別,以帶有箭號的線條連接。相依性是單向關係,也就是說類別A相依於類別B(此時箭號指向B),不代表類別B也同樣相依於類別A。
從相依圖中可以觀察出程式碼在設計上的一些問題。例如,當某個類別被許多箭頭所指向時,意謂它是受到高度依賴的類別。也就是說,當這個類別有所變動時,可能影響的類別數量就會很多。
換一個角度來看,一個類別高度依賴其他類別,同樣也不是一件好事。這意謂著這個類別有可能因此頻繁地更動,因為只要任一個它所依賴的類別有了變化,它都可能被波及。此外,像循環相依(Circular Dependency)也是另一個常見的設計問題。
使用相依圖,對於分析既有系統的某些設計問題,能夠發揮一些作用。不過,盡管如此,此類的工具多半屬於靜態分析的形式,也就是說,不論是在原始碼層級或位元碼層級上分析,這樣的工具都只能分析靜態的程式碼,無法捕捉系統執行期的行為。
共通介面能扮演緩衝,吸收實作類別造成的衝擊
當你在設計類別以及類別所具備的介面時,必須時時刻刻把類別間的相依性放在心裡。當你安置類別與類別的關係時,你可能會想到:「唔,這麼做的話,豈不是讓那些類別都和這個類別有了相依性嗎?」因而促使你思考如何降低類別間的相依性,進而持續改善設計。許多導因自相依性的設計問題,多半都是由於設計者不在意,或是不知道應該要留意類別的相依性而引起。
在有名的GoF的《Design Patterns》一書中,提到設計時的重要原則:「針對介面來撰寫程式,而不要針對實作(Program to interface, not an implementation)」。這中間的原因,如果從相依性的角度理解,就十分清楚了。
倘若你的客戶端程式相依5個實作的類別,那麼它在相依圖上,就有著5條對外的連結。如果這5個實作類別有共通的介面,那麼你就有機會讓客戶端程式僅相依於該介面,大大降低相依性的程度。即便後端的實作類別有所變動,共通的介面也能扮演著緩衝的角色,有機會吸收掉實作類別造成的衝擊,也就不致於影響客戶端程式。
由此可以觀察到另一個特性──在相依圖上兩類別間若存在一條路徑,那麼當它們之間的距離越遠,對其中一個類別修改,會波及到另一個類別的機會就會越低。兩類別之間間接的層級越高,越能抵抗改變的影響。
不過另一方面,程式中若處處充滿間接的設計,也會衍生出效率及可讀性的問題。類別間訊息傳遞路徑太長,自然沒有效率。而太高的間接層級,也有可能造成閱讀的理解障礙。
運用Façade模式,可以有效降低相依性
有許多設計模式,都是把重點放在降低類別之間的相依性。例如有名的Façade模式,便是為了消除客戶端類別與許多位於同一子系統中類別的相依性而設計的。
這種Façade設計模式希望所有的客戶端程式碼對某個子系統的存取,一律從單一窗口進入,而不要直接存取背後所有相關的實作。當客戶端程式碼只直接相依於Facade類別時,Façade類別就做為一道防火牆。當子系統實作或內部設計改變時,Façade便有機會吸收這些改變。
倘若沒有Façade,而放任客戶端程式碼直接存取子系統中的類別時,那麼子系統的變動,可能會同時造成多個子系統的類別需要改變,同時由於客戶端程式碼建立了許多條連至各個類別上的連結,所以各個類別的改變,都可能造成客戶端程式碼得同時做出許多的因應與調整。
因此,運用Façade模式,可以有效地降低相依性,也降低客戶端程式碼在子系統有所變化時受到影響的機會。
作者簡介:
王建興
清華大學資訊工程系的博士研究生,研究興趣包括電腦網路、點對點網路、分散式網路管理、以及行動式代理人,專長則是Internet應用系統的開發。曾參與過的開發專案性質十分廣泛而且不同,從ERP、PC Game到P2P網路電話都在他的涉獵範圍之內。