Encapsulation
🪵 降低耦合
所謂「降低耦合」,就是讓物件的使用者能很直覺地透過公開的方法去操作,而不用關心內部的細節。這麼做有兩個好處:
- 使用者能專注在更高層次的抽象,而不是陷在物件內部的實作。
- 後續如果內部邏輯要修改,開發者比較不用擔心會影響到外部程式。
購物車
1 | public class ShoppingCart |
如果直接把集合公開,外部程式就能為所欲為:
隨便新增重複的商品、甚至一次把所有商品刪光,這樣一來,我們完全無法控制購物車的正確狀態。
透過封裝,我們只提供必要的方法:
1 | public class ShoppingCart |
現在使用者只能透過 AddItem() 加入商品,我們就能保證「不會有重複」。未來如果還要加上「庫存檢查」或「折扣券驗證」,只要在 AddItem() 裡擴充就好,外部程式碼完全不用改。
遊戲角色
1 | public class Player |
如果血量是公開欄位,外部程式隨便一改就能把角色狀態弄壞,變得不合法。
我們把欄位封裝起來,提供合理的操作介面:
1 | public class Player |
現在,外部程式不能隨便改血量,只能透過 TakeDamage()。這樣我們就能確保遊戲角色永遠維持「健康 ≥ 0」的狀態。
我們希望將控制權掌握在物件本身。也就是說封裝的關鍵在於:
- 限制外部能做的事情(避免直接操作內部狀態)
- 提供抽象、穩定的操作界面(集中邏輯,方便維護與擴充)
🪵 get / set
在 C# 裡,屬性(Property)表面上看起來跟欄位(Field)很像,但本質上卻是由 get 與 set 方法所構成的存取器。這兩個方法扮演的是「資料進出的守門人」:
- set → 控制資料寫入物件時的規則(例如檢查長度、範圍、格式)。
- get → 控制資料讀取出物件時的呈現方式(例如格式化、運算、遮罩處理)。
這樣做的目的,不僅僅是「把邏輯塞進去」,更是確保 資料的一致性 與 維護的集中化。
例如一個薪資管理系統
1 | public class Employee |
需求是這樣子,公司規定薪資顯示時必須四捨五入到整數
如果是公開欄位 → 每個顯示 Salary 的地方都得手動 Math.Round(emp.Salary, 0)。系統中只要有 50 個頁面用到薪資,就得改 50 次!
如果我們使用 property 的話
1 | public class Employee |
- 只需要在 get 改一次 → 所有使用薪資的地方自動符合規則。
- 封裝確保「顯示薪資 = 四捨五入」的邏輯集中在一處。
🪵 Property 與 Field
在實際開發中,為什麼大家習慣用 Property 而不是 Field?我們可以從幾個角度來看。
編譯後的差異
- Field:直接對類別的記憶體位置進行讀寫,編譯器生成的就是單純的讀取/寫入指令。
- Property:實際上會被編譯成 get_Xxx() / set_Xxx(value) 兩個方法,程式呼叫時是在執行方法。
也就是說,語法上看起來一樣 (obj.Name),但在 IL(中繼語言)層級,兩者完全不同。
這帶來一個影響:如果某個 DLL 一開始提供的是 field,後來改成 property,呼叫端舊程式就會找不到那個欄位,必須重新編譯才行。在早期的 .NET Framework 時代,常見的情境是「直接替換 DLL 升級,不重新編譯主程式」。這種情況下,field → property 的改動會導致 相容性問題。
而在現代的 Web App 環境,每次部署都會完整重新 build,因此這個問題就沒那麼嚴重了。
工具與框架支援
另一個更實際的理由是:大部分主流框架都支援 Property,而不是 Field。
JSON Serializer (System.Text.Json / Newtonsoft.Json)
1 | using System.Text.Json; |
預設情況下,System.Text.Json 只會序列化 public property,不會處理 field。如果用 field,輸出的 JSON 是 {},資料就丟失了。(Newtonsoft.Json 預設也只處理 property,要啟用 [JsonProperty] 才能序列化 field。)
Entity Framework (EF Core)
1 | public class ProductWithProperty |
EF Core 預設會把 public property 當作 column mapping,但 public field 不會被自動 mapping(除非你用 Fluent API 指定 builder.HasField(“_name”))。結果就是你的 Name 根本不會存進資料庫。
因此,以 Property 來說,主流框架(JSON、EF、WPF、ASP.NET)都支援。而 Field = 預設不會被處理,必須額外寫設定、Attribute、Fluent API。所以 convention 上大家才會說:「公開的東西就用 Property,Field 永遠 private」。
🪵 結語
軟體設計,就像蓋一棟房子。
如果把所有的水管和電線都攤在外面,住戶的確能隨手接一條線、隨便裝一個插座,但最後一定會變成危險又難以維護的混亂現場。而封裝(Encapsulation)就像是把管線藏在牆裡,只留下開關和插座,讓使用者可以直覺、安全地操作,不必擔心內部的複雜結構。
Property 與 Field 的選擇,其實也反映了這種設計哲學。Field 像是裸露的電線,能直接動用,但沒有任何保障。Property 則像是規劃好的開關,使用簡單,背後卻有統一的規則在守護。在開發過程中,封裝讓我們能把控制權留在物件本身,不僅保護了資料的一致性,也讓未來的修改不至於牽一髮而動全身。
🪵 參考文章
菜雞與物件導向 (3): 封裝Understanding encapsulation benefits of properties in C#