1. 控制存取權限
封裝 不是「什麼都藏起來」,而是 給出可控的存取權限。
很多新手會以為封裝就是把所有欄位都設成 private。沒錯,欄位通常會 private,但這不是全部。重點不是單純 隱藏 資料,而是 管理存取。
有時候這代表完全禁止修改(像是 get-only 屬性)。有時候允許讀寫但要驗證。有時候只允許 class 內部讀寫(public Type Property { get; private set; })。
封裝讓你可以為你的物件訂「遊戲規則」:決定怎麼改它、保證每次改動都會讓它保持有效狀態。
在實際專案裡,尤其是大團隊,沒有封裝就會亂成一團。每個人都能直接改別人的物件內部狀態,錯誤和衝突會爆炸多。封裝讓每個模組(class)都能自給自足,自己負責自己的資料。
這是 SOLID 原則的基石之一,特別是 單一職責原則 (Single Responsibility Principle) 跟 開放封閉原則 (Open/Closed Principle),之後你會學到。現在只要記住,封裝讓你的程式碼更強壯、彈性又好懂、好維護。
// 封裝的目標:在重要資料外面蓋一層隱形柵欄
// 沒人能不小心從外部「搞砸」!
封裝就像在你的物件重要資料外面蓋一層隱形柵欄,沒人能不小心搞砸。你總不希望你的狗突然有負的年齡吧?封裝就是在幫你顧這個。它也讓 class 用起來很簡單:其他工程師不用進去研究內部怎麼寫,只要用你給的東西就好。最酷的是,明天你想改 class 內部,比如把年齡從單純數字改成存生日,外部根本不會發現,因為介面沒變。這才是封裝真正的價值!
2. 封裝的好處
為什麼這對你的專案和職涯這麼重要?
資料完整性 (Data Integrity):
封裝可以確保物件的資料永遠是正確、合理的狀態。這會大幅減少 bug。面試被問 OOP,如果你能好好解釋封裝怎麼幫助資料完整性,超加分!
彈性和好維護 (Flexibility & Maintainability):
想像一下,你一開始把狗的年齡存成 int Age。過一年,老闆說:「我們要知道狗的生日,不要年齡!」
沒封裝的話: 如果 Age 是 public int,你程式裡一堆地方都直接用 myDog.Age。你得全部改成 DateTime,還要重寫年齡計算邏輯。超痛苦!
有封裝的話: 如果你有 private int _age; 跟 public int Age { get; set; },你只要把內部欄位改成 private DateTime _dateOfBirth;,然後重寫 get 跟 set 的邏輯,讓 Age 變成用生日算出來。外部用 myDog.Age 的程式 完全不用改,因為他們只碰到 public 介面 Age,沒直接碰欄位。這就叫 降低耦合 (loose coupling)。
看到沒,超神奇?我們換了 class「內餡」,但「包裝」(public 介面)沒變!
更好 debug (Easier Debugging):
如果物件資料出問題,有好好封裝的話,你知道問題只會出在 class 自己的方法或 public 屬性。找 bug 只要看一個 class,不用全世界找。
API 設計更棒:
封裝 class 時,你會明確定義哪些是 public「合約」(API),哪些是內部細節。這讓你的 class 介面乾淨、可預期、好懂。API 越清楚,別人(或你未來自己)用你的 code 就越輕鬆。
安全性 (Security):
雖然 C# 不是像 C++ 那種超講究安全的系統語言,封裝還是很重要。它讓你能控制誰、怎麼碰你的資料,避免亂改或偷看機密資料(如果有的話)。
3. C# 怎麼做封裝?
在 C# 裡,封裝主要靠這幾招:
存取修飾詞 (private, public 等):
我們把 class 欄位設成 private,這樣外部不能直接改。這就是 資訊隱藏 (information hiding)。
我們把 方法和屬性設成 public,這樣才能給外部可控的存取權限。這就是 public 介面。
屬性 (Properties):
你學過,屬性就是 get(讀)和 set(寫)方法的語法糖。它讓你把欄位藏起來,但還是能透過 public 外觀存取。最重要的是,set 裡可以加驗證邏輯!
範例:有封裝的 Dog class
先來寫個 錯誤示範(沒封裝):
public class Dog
{
public string Name;
public int Age;
public void Bark()
{
Console.WriteLine($"{Name} 說:汪!");
}
}
這種寫法,其他 class 可以隨便改狗的名字和年齡。例如:
Dog dog = new Dog();
dog.Name = ""; // 可以設成空名字!
dog.Age = -100; // 可以讓狗變超「古老」
現實生活很少這樣,但 code 裡常常發生,如果你沒想過封裝。
保護資料:欄位設成 private
把欄位設成 private。這樣除了 class 自己,誰都不能直接改:
public class Dog
{
private string _name;
private int _age;
public void Bark()
{
Console.WriteLine($"{_name} 說:汪!");
}
}
這樣就不能這樣用了:
Dog dog = new Dog();
dog._name = "Rex"; // 編譯錯誤!
透過屬性 (Properties) 存取資料
但你還是想知道狗的名字(比如要印出來),有時也要改(換主人或個性變了)。這時就用屬性!
public class Dog
{
private string _name;
private int _age;
public string Name
{
get { return _name; }
set
{
// 加驗證:名字不能是空的
if (string.IsNullOrWhiteSpace(value))
{
Console.WriteLine("錯誤:名字不能是空的!");
}
else
{
_name = value;
}
}
}
public int Age
{
get { return _age; }
set
{
// 實際一點:年齡不能是負的!
if (value < 0)
{
Console.WriteLine("錯誤:年齡不能是負的!");
}
else
{
_age = value;
}
}
}
public void Bark()
{
Console.WriteLine($"{_name} 說:汪!");
}
}
現在資料有保護,但你還是可以透過 可控介面 用它們:
Dog dog = new Dog();
dog.Name = "Barbos"; // OK!
dog.Age = 3;
dog.Name = ""; // 會顯示錯誤,欄位不會變!
dog.Age = -1; // 又錯誤
自動屬性
如果你不需要額外驗證邏輯,可以不用寫欄位,直接用自動屬性:
public class Dog
{
public string Name { get; set; }
public int Age { get; set; }
public void Bark()
{
Console.WriteLine($"{Name} 說:汪!");
}
}
這時 Name 和 Age 其實也「被封裝」了——你只能透過屬性讀寫,不能直接碰欄位。
4. 封裝行為:只把需要的方法公開
封裝不只資料,還有方法!有些方法是 class 內部的小齒輪,根本不該給外部看。
範例:
public class Dog
{
// 只給內部用
private void WagTail()
{
Console.WriteLine("狗在搖尾巴。");
}
// 給大家用
public void Bark()
{
WagTail(); // 在 class 裡呼叫隱藏方法
Console.WriteLine("汪!");
}
}
現在除了狗自己,誰都不能直接讓牠搖尾巴:
Dog dog = new Dog();
dog.WagTail(); // 錯誤!方法是 private。
dog.Bark(); // Bark 裡面會呼叫 WagTail
總結:欄位、方法、屬性和可見性
來整理一下:
| class 部分 | 可以設 private 嗎? | 可以設 public 嗎? | 為什麼要限制存取? |
|---|---|---|---|
| 欄位 | 可以 | 可以(但不要!) | 保護資料 |
| 方法 | 可以 | 可以 | 隱藏實作細節 |
| 屬性 | 可以 | 可以 | 控制讀寫 |
建議:欄位都設 private,只透過屬性或方法給外部用。
GO TO FULL VERSION