自我封裝
2017 年 3 月 9 日
資料封裝是物件導向風格中的核心原則。這表示物件的欄位不應公開顯示,所有來自物件外部的存取都應透過存取方法(取得器和設定器)進行。有些語言允許公開存取的欄位,但我們通常會告誡程式設計師不要這樣做。自我封裝更進一步,表示對資料欄位的所有內部存取也應透過存取方法進行。只有存取方法才能觸及資料值本身。如果資料欄位未公開顯示,這表示需要新增其他私人存取器。
以下是合理封裝的 Java 類別範例
class Charge…
private int units; private double rate; public Charge(int units, double rate) { this.units = units; this.rate = rate; } public int getUnits() { return units; } public Money getAmount() { return Money.usd(units * rate); }
兩個欄位都是不可變的。units 欄位透過取得器公開給類別的用戶端,但 rate 欄位只在內部使用,因此不需要取得器。
以下是使用自我封裝的版本。
class ChargeSE…
private int units; private double rate; public ChargeSE(int units, double rate) { this.units = units; this.rate = rate; } public int getUnits() { return units; } private double getRate() { return rate; } public Money getAmount() { return Money.usd(getUnits() * getRate()); }
自我封裝表示 getAmount
需要透過取得器存取兩個欄位。這也表示我必須為 rate
新增取得器,而我應將它設為私人。
封裝可變資料通常是個好主意。更新函式可以包含執行驗證和後續邏輯的程式碼。透過限制函式存取,我們可以支援 UniformAccessPrinciple,讓我們可以隱藏哪些資料是計算出來的,哪些是儲存的。這些存取器讓我們可以在保留相同公開介面的同時修改資料結構。不同的語言在「外部」是什麼的細節上有所不同,取決於各種 AccessModifier,但大多數環境在某種程度上都支援資料封裝。
我遇到過一些強制使用自我封裝的組織,而是否要使用它自 90 年代以來一直是定期辯論的話題。它的支持者表示封裝是一個好處,你想要將它納入內部存取中。批評者則認為這是不必要的儀式,導致不必要的程式碼,模糊了正在發生的事情。
我對此的看法是,大多數時候自我封裝的價值很低。封裝的價值與資料存取的範圍成正比。類別通常很小(至少我的類別很小),因此直接存取在該範圍內不會是個問題。大多數存取器都是設定項的簡單指派和取得項的擷取,因此在內部使用它們的價值很低。
但在一些常見情況下,自我封裝是值得付出的。如果設定項中有邏輯,那麼也明智地考慮對任何內部更新進行設定。另一種情況是當類別是繼承結構的一部分時,在這種情況下,存取器為子類別提供有價值的掛鉤點,以覆寫行為。
因此,我通常的第一步是使用直接存取欄位,但如果情況需要,則使用 自我封裝欄位 進行重構。通常,促使我考慮自我封裝的力量,我可以透過 萃取新類別 來解決。
進一步閱讀
Kent Beck 在 Implementation Patterns 和 Smalltalk Best Practice Patterns 中以直接存取和間接存取的名稱討論了這些權衡。