1. 前言
想像你在設計一台現代汽車。裡面有超多複雜零件、敏感電子設備、細緻的設定。很明顯,沒有人應該隨便打開引擎控制單元亂轉螺絲。如果大家都亂動,車子可能會變得很怪,還要你來檢查到底是誰弄壞了什麼。
寫程式也是一樣。當你寫一個 class,你會想保護它的內部,不讓別人亂動重要資料,或是去碰那些外部根本不該知道的方法。這就是 封裝 的重點——物件導向程式設計三大原則之一。
封裝可以把實作細節藏起來,明確規定哪些東西可以「給外面用」,哪些要留在裡面。就像你只讓使用者打開手套箱,但引擎蓋鎖得死死的。class 自己決定哪些資料要給外部 code 看,哪些要保密。這樣 code 就更穩、更有邏輯,也更好維護。
嚴格來說,封裝 就是把資料(欄位)和跟它有關的行為(方法)合在一個結構裡,同時限制直接存取物件內部元件的方式。
2. 存取修飾詞:守護自己的地盤
在 C#(還有其他 OOP 語言)裡,封裝 是靠 存取修飾詞 來實現的。這些「標籤」決定 class 的哪些成員(欄位、方法、屬性等等)可以被外部看到,哪些只能在內部用。
我們之前有碰過,這裡再提醒一下,順便補充一下常見的修飾詞:
| 修飾詞 | 可存取範圍... |
|---|---|
|
大家都可以!專案裡任何 code(如果是 library,外部也可以) |
|
只有這個 class 裡面可以用 |
|
這個 class 跟它所有繼承的子類都可以用 |
|
只有這個組件(專案)裡面可以用 |
|
要嘛在這個組件裡、要嘛在繼承的子類裡都可以用 |
|
只有這個組件裡的繼承子類可以用 |
這堂課我們主要看最常用的:public、private,還有稍微提一下 protected,不會讓你腦袋爆炸。
分清楚內部跟外部
來寫個 Dog class:
public class Dog
{
public string Name;
public int Age;
public void Bark()
{
Console.WriteLine($"{Name} 說:汪!");
}
}
這裡所有欄位和方法都是 public。意思是,任何人都可以改狗的名字或年齡:
Dog rex = new Dog("雷克斯", 5);
rex.Name = "小黑"; // 突然換身份!
rex.Age = -999; // 年齡出大事
但 Name 跟 Age 這兩個欄位超重要,不能隨便給人亂改。
這樣寫很危險
如果全部欄位都 public,class 的邏輯很容易被破壞:外部亂動就可能讓物件變得不合法。比如給狗設負數年齡,或名字設成 "%$#!??" 這種鬼東西。
3. 把欄位藏起來:用 private
通常 class 的欄位都會設成 私有(private)。這樣只有 class 自己能改,外面完全沒辦法動。
public class Dog
{
private string name;
private int age;
public void Bark()
{
Console.WriteLine($"{name} 說:汪!");
}
}
現在如果外部想直接動欄位:
Dog rex = new Dog("雷克斯", 5);
rex.name = "小黑"; // 編譯錯誤
編譯器直接跟你說:「沒門!」
為什麼要這麼嚴?彈性去哪了?
「彈性」是靠特別的方法或 屬性(下一堂課會講)來控制的,這樣才能照規則改資料。
4. 封裝實戰:加點檢查
假設我們想讓狗的年齡不能設成負數:
public class Dog
{
private string name;
private int age;
public Dog(string name, int age)
{
this.name = name;
if (age >= 0)
this.age = age;
else
this.age = 0; // 不讓你設奇怪的年齡
}
public void Bark()
{
Console.WriteLine($"{name} 說:汪!");
}
// 安全改年齡的方法
public void SetAge(int newAge)
{
if (newAge >= 0)
age = newAge;
// 也可以加 else:提示值不對
}
}
現在外部不能直接亂動欄位。要改年齡只能用特定方法,還會檢查值對不對。
5. 欄位 vs 存取方法(getter/setter)
這種把欄位設 private,再提供方法來操作的方式,叫做 資料封裝(data encapsulation)。要讀欄位就寫 getter,要改就寫 setter。
public class Dog
{
private string name;
public Dog(string name)
{
this.name = name;
}
public string GetName()
{
return name;
}
public void SetName(string newName)
{
// 這裡可以加名字檢查
name = newName;
}
}
不過! C# 有 properties 之後,這種方法就比較少用了——屬性讓 code 更乾淨好用(下堂課會細講)。
6. 方法和 class 也有存取修飾詞
不只欄位!方法(class 的成員函式)甚至 class 本身都可以設存取修飾詞。
只給 class 內部用的方法(像是內部邏輯的小工具),通常都設 private。
公開的方法——這就是 class 的介面(不是 interface 關鍵字那個),也就是給 class 使用者用的東西。
7. 封裝常見錯誤
錯誤一:什麼都設 public。
以為越多東西能用越方便,其實這樣會讓 class 內部暴露,外部亂改就很危險,尤其專案大了更容易出事。
錯誤二:改了存取修飾詞,外部 code 爆炸。
重構時不小心改到方法或欄位的修飾詞,結果其他 code 都不能編譯或行為怪怪的。公開 API 特別要小心這點。
錯誤三:搞混區域變數和 class 欄位。
有時候會忘記方法裡宣告的變數只活在那個方法裡,而 class 欄位整個 class 都能用。如果名字一樣更容易出 bug。
錯誤四:不敢用 private 跟 protected。
很多人怕設太嚴,之後用不到。但封裝就是要把多餘的東西藏起來,只把真的需要的東西給外面用。
GO TO FULL VERSION