UML 套件圖:細說 «import» 與 «access»
UML 套件圖的 «import» 與 «access» 概念雷同,但語意略有不同,這篇文章主要即在說明這兩個 stereo types 的差異。本文的圖都是引用自《Object-Oriented Analysis and Design with Applications》的第 5 章,部分有稍微修改(以免圖太大)。
UML 套件圖基本觀念
在進入細節之前,先來看一下 UML 套件圖的基本概念。
UML 套件的一個主要用途,是展現系統各元素(例如:子系統、模組、元件等)的組織與分割方式。套件裡面可以包含其他子套件而形成巢狀套件,也可以包含其他 UML 圖形元素,例如:類別、Use Case 等。當我們使用套件圖來表達系統的邏輯或實體結構時,可能會需要在圖中呈現套件之間的相依關係,例如下面這張套件圖:
此套件圖呈現的是一個園藝系統的套件結構,裡面包含兩個子套件:Planning 和 CropTypes,而這兩個套件裡面又包含了一些類別。相依關係的符號是虛線加上一個開放箭頭,圖中箭頭的方向表示 Planning 套件內的元素會用到 CropTypes 套件內的元素。這個「用到」的意思,以比較偏實作的術語來說,通常就是「呼叫方法」、「存取屬性」的意思。在表達分析概念時,我們比較常用抽象的、更一般化的術語,例如:參考、引用等。
「存取」也好,「引用」也罷,究竟它有什麼值得討論的地方?就上圖而言,這表示 Planning 套件中的程式碼在使用 CropTypes 套件中的元素時,必須指名欲存取的元素名稱,例如: CropTypes::CropDatabase 或 CropTypes.CropDatabase。這種從外層套件開始指定每一層元素的名稱,即稱為限定名稱(qualified name);而每一層套件,也就由其名稱構成一個存取範圍,這個範圍就是所謂的命名空間(namespace)。
什麼是 «import»
在撰寫程式的時候,如果都得使用 qualified name 來指名某個類別,這未免太累了。因此,許多物件導向程式語言都有提供引用其他套件(模組、命名空間....whatever)的語法來減輕程式設計的負擔(程式碼也更簡潔),例如 Java 的 import、C# 的 using、VB.NET 的 Imports 等等。對應到分析設計領域,使用的術語就是「匯入」(import)。也就是說,一旦你在某個類別中指定匯入其他套件(命名空間),當你要存取匯入套件的類別時,就不用指定 full qualified name,而只要寫類別名稱就可以了。
說到這裡,還是得提一下 visibility。我們可以指定元素的 visibility 來控制它可以被誰看見,像圖中的 CropDatabase 類別名稱前面有個加號('+'),就代表它是公開的(public),即誰都可以存取;PlanAnalyst 前面有個減號('-'),代表它是私有的(private),亦即只有它所屬的命名空間(套件)中的元素可以存取它。
呼~基本概念就講到這裡,快點進入主題吧....
«import» 與 «access»
一言以蔽之:«import» 就是公開的 «use»;«access» 則是私有的 «use»。
聽起來有點像在繞口令,我用另一張圖來解釋。
這張圖是上一張圖的進一步描述,裡頭的相依關係都標上了 «import» 或 «access»。根據剛才「一言以蔽之」的說法,圖中 Planning 套件是以公開的方式使用 CropTypes 套件中的類別,而以私有的方式使用子套件 Plans 裡的類別。說得更明白些,Plans 套件裡的類別就只有 Planning 套件看得見;Greenhouse 套件雖然有 import Planning,但它看不見 Plans 裡面的類別。
也就是說,«import» 與 «access» 的主要差異在於遞移性(transitivity)。 «import» 是可遞移的關係,舉例來說,若套件 A import B,B 又 import C,則表示 A 也隱含地 import 了 C,換言之,套件 A 中的類別可直接存取套件 C 中的類別。相對的,«access» 則沒有遞移性,所以上圖中的 Greenhouse 套件無法使用 Plans 套件中的類別。
對應到實作
那麼,如果在分析設計時使用了 «import» 或 «access» 來表達套件之間的關係,對應到實作時會是什麼語法呢?
以 .NET 的 C# 語言來說,如果所有引用的類別都宣告為 public,那就是 «import» 關係;如果某個類別宣告為 internal,亦即只有該類別所在的組件(assembly)才能看見它,那麼它就是僅供同一組件中的類別使用,這便符合了 «access» 的語意。
同樣以前面的圖例來說明,如果將 Planning 套件實作成一個 .NET 組件,那麼它裡面的兩個子套件就代表該組件中的兩個命名空間,而且 Plans 命名空間裡面的兩個類別 GardeningPlan 和 PlanMetrics 都是宣告為 internal(儘管圖中是以 '+' 表示公開)。如此一來,就只有 Planning 裡面的類別可以存取 Plans 的 GardeningPlan 和 PlanMetrics,其他外部類別則無法存取。
Java 語言支援的四種 visibility 層級中,package-private 的作用即類似 C# 的 internal。
或許有人會覺得,軟體專案本身已經夠複雜了,為何要弄個 «import» 或 «access» 語法來折磨自己(和別人),全都用預設的 «use» 不就結了?
這就跟你不會把類別的所有成員都宣告為 public 的理由一樣:封裝與資訊隱藏。在稍微複雜一點的系統中,切分套件時一定會碰到有些小型套件僅供特定對象使用,這時候當然就不需要將它公開。資訊太多,對人和電腦其實都是一種負擔。
設計師的工作就是要以簡馭繁,我對這句話的解讀是:把繁複的思考工作攬下來,設計出簡單好用的類別,讓使用類別的人覺得簡單。這時候,「簡單就是美」就不能拿來當作偷懶的理由了(雖然我們做軟體開發的其實都是在想辦法「偷懶」),否則每次碰到大量資料時開個超大陣列就好了,又何必學資料結構和演算法呢?