SOLID 原則:好的程式碼長怎樣?
SOLID 不是教你「怎麼寫」,而是教你「什麼時候該拆、該抽、該改」。
S — 單一職責原則(Single Responsibility)
一個類別只應該有一個改變的理由。
// ❌ 壞:一個類別做太多事
public class UserService {
public void CreateUser(User user) { /* 建立使用者 */ }
public void SendWelcomeEmail(User user) { /* 寄信 */ }
public string GenerateReport() { /* 產報表 */ }
}
// ✅ 好:拆成各自負責
public class UserService { public void CreateUser(User user) { } }
public class EmailService { public void SendWelcome(User user) { } }
public class ReportService { public string Generate() { } }
判斷方法:描述這個類別時,如果用到「而且」就該拆。 「UserService 負責建立使用者而且寄信而且產報表」→ 拆!
O — 開閉原則(Open/Closed)
對擴展開放,對修改封閉。
// ❌ 壞:每加一種折扣就要改 if-else
public decimal CalculateDiscount(string type, decimal price) {
if (type == "vip") return price * 0.8m;
if (type == "student") return price * 0.9m;
// 新增類型 → 改這裡 → 可能影響其他
}
// ✅ 好:用多型擴展
public interface IDiscount { decimal Apply(decimal price); }
public class VipDiscount : IDiscount { public decimal Apply(decimal price) => price * 0.8m; }
public class StudentDiscount : IDiscount { public decimal Apply(decimal price) => price * 0.9m; }
// 新增折扣 → 新增類別 → 不改舊程式碼
L — 里氏替換原則(Liskov Substitution)
子類別必須能完全替代父類別使用。
// ❌ 壞:正方形繼承長方形,但行為不一致
class Rectangle { virtual void SetWidth(int w); virtual void SetHeight(int h); }
class Square : Rectangle {
override void SetWidth(int w) { Width = w; Height = w; } // 改寬也改高 → 違反預期
}
// 呼叫端預期:改寬不影響高
Rectangle r = new Square();
r.SetWidth(5);
r.SetHeight(10);
// 預期面積 50,實際面積 100 → 破壞了替換性
判斷方法:把子類別的物件傳到只認識父類別的函式,行為會不會「出乎意料」?
I — 介面隔離原則(Interface Segregation)
不要強迫類別實作它用不到的方法。
// ❌ 壞:一個大介面
public interface IWorker {
void Work();
void Eat();
void Sleep();
}
// 機器人要實作 Eat()?Sleep()?
// ✅ 好:拆成小介面
public interface IWorkable { void Work(); }
public interface IFeedable { void Eat(); }
public class Robot : IWorkable { public void Work() { } }
public class Human : IWorkable, IFeedable { public void Work() { } public void Eat() { } }
D — 依賴反轉原則(Dependency Inversion)
高層模組不應依賴低層模組,兩者都該依賴抽象。
❌ OrderService → SqlOrderRepository(直接依賴具體類別)
✅ OrderService → IOrderRepository ← SqlOrderRepository(都依賴介面)
這就是 DI 的理論基礎。
SOLID 不是教條
| 原則 | 過度使用的症狀 |
|---|---|
| SRP | 一個方法拆成 10 個類別,看不懂流程 |
| OCP | 三行 if-else 也要搞 Strategy Pattern |
| LSP | 完全不用繼承(其實簡單場景繼承很好用) |
| ISP | 每個介面只有一個方法,介面數量爆炸 |
| DIP | Console App 也搞 DI Container |
原則是指南,不是法律。先讓程式碼能動、好讀,再考慮 SOLID。