在我們熟悉的OO語言中,可以通過private、protected、public等存取控制修飾符將資料和方法分為內部可見、子類可見、外部可見等不同存取層級。本文從一個較為特別的封裝相關例子出發,討論封裝、類型系統、契約式編程相關話題。我們先從例子開始:
public class Person {
private int _money;
public void Change(amount){
this._money += amount;
}
public void Exchange(Person p, int amount) {
p._money -= amount;
this._money += amount;
}
}
基於”沒有經過我允許,不許直接動我的錢”的假設,Person類把變數_money作為private成員,並提供public的Change方法。但有爭議的地方在於,在Exchange中我們可以直接存取並修改p._money。請注意上面的代碼在C#編譯器下是完全合法的,類似的代碼在C++和Java下也一樣。我在第一次接觸這個例子的時候,起初有些疑惑編譯器是對還是錯?後來想了一個理由“OO的封裝是基於類的,而非對象”來說服自己。
現在,對這個問題有了更新的認識,希望和大家討論。上面的例子中,我們的理想情況是Person對象本身可以直接存取自己的_money變數,其他Person對象不能直接存取。但OO的封裝實現必須依賴編譯器進行靜態存取權限檢查;既然是靜態那就只能應用到類型,無法應用到對象執行個體。因此,在上例中C#編譯器無法確認p和this是否引用同一對象或不同對象。從這個意義上講,OO的封裝只到類這一層級與其說是設計考慮不如說是不得以的選擇。
從更大的層面上講,這個例子不僅和OO封裝相關,本質上是反映了程式語言類型系統的作用和局限。程式語言的類型系統是一種靜態跟蹤和檢查機制,它能保證程式的類型正確性,但無法保證語義正確性(理想情況下,上面的例子中,p如果和this是同一對象那麼語義正確;否則,語義錯誤)。換句話說,類型正確性可以通過類型系統用形式化的方式進行表達和檢查。那麼語義正確性應該如何來保證呢?有沒有形式化的方式呢?答案也許就像下面這樣:
public class Person {
private int _money;
public void Change(amount){
this._money += amount;
}
public void Exchange(Person p, int amount) {
Assert(this != p);
p.Change(-amount);
this._money += amount;
}
}
增加Assert斷言,就是希望從形式上保證語義的正確性。我的理解是:我們需要一種形式化的方式保證語義的正確性。這是不是就是所謂契約式編程的初衷呢?希望高手指點!