面向對象程式設計 (OOP) 的靈魂在於封裝、繼承、多態三大特性。它們不只是語法糖,更是管理軟體複雜度、實現高內聚低耦合的核心工具。本文結合 C# 語言特性,從原理、語法、設計意圖到完整實戰範例,帶你透徹掌握 OOP 精髓。
一、封裝:邊界的藝術
核心定義
「將資料與操作資料的方法綁定在一起,對外僅暴露必要介面,隱藏內部實現細節。」
C# 存取修飾詞體系
| 修飾詞 | 可見範圍 | 典型用途 |
private | 僅本類別 | 內部欄位、輔助方法 |
protected | 本類別 + 衍生類別 | 供子類擴充的核心邏輯 |
internal | 同一組件 | 組件內部服務協定 |
protected internal | 同組件 或 衍生類別 | 跨組件繼承場景 |
public | 任意處 | 對外公共 API |
private protected | 同組件且衍生類別 | 極度受限的擴充點 |
屬性封裝模式
public class BankAccount
{
private decimal _balance; // 隱藏實現細節
public decimal Balance
{
get => _balance;
private set // 外部不可直接修改
{
if (value < 0) throw new ArgumentException("餘額不可為負");
_balance = value;
}
}
public void Deposit(decimal amount)
{
if (amount <= 0) throw new ArgumentException("存款金額需為正");
Balance += amount; // 內部可用 private set
}
public bool Withdraw(decimal amount)
{
if (amount > Balance) return false;
Balance -= amount;
return true;
}
}
封裝三大收益
- 安全性:不變條件由類別內部守護,外部無法輸入非法狀態
- 可維護性:內部演算法變更 (如改用資料庫、快取) 不影響呼叫端
- 降低耦合:呼叫端依賴抽象介面而非具體實現
二、繼承:擴充與分類
語法與語意
public abstract class Shape // 抽象基類:定義契約、共享邏輯
{
public string Name { get; protected set; }
protected Shape(string name) => Name = name;
// 模板方法:定義演算法骨架,延遲具體步驟給子類
public void PrintInfo()
{
Console.WriteLine($"{Name}: Area={CalculateArea():F2}, Perimeter={CalculatePerimeter():F2}");
}
public abstract double CalculateArea(); // 強制子類實現
public abstract double CalculatePerimeter(); // 強制子類實現
// 可選:提供預設實現供子類複寫
public virtual void Draw() => Console.WriteLine($"Drawing {Name}...");
}
衍生類別實作
public class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius) : base("Circle") => Radius = radius;
public override double CalculateArea() => Math.PI * Radius * Radius;
public override double CalculatePerimeter() => 2 * Math.PI * Radius;
public override void Draw() => Console.WriteLine($"🔴 繪製半徑 {Radius} 的圓");
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double w, double h) : base("Rectangle") { Width = w; Height = h; }
public override double CalculateArea() => Width * Height;
public override double CalculatePerimeter() => 2 * (Width + Height);
}
繼承 vs 組合:設計決策表
| 場景 | 選擇 | 理由 |
| 「是一種」關係明確 (Circle 是 Shape) | 繼承 | 語意符合 Liskov 替換原則 |
| 需要複用部分行為但非完整契約 | 組合 | 避免破壞封裝、減少耦合 |
| 基類頻繁變更 | 組合 | 脆弱基類問題 |
| 多重行為組合 (可飛、可游、可叫) | 介面 + 組合 | C# 不支援多重繼承 |
三、多態:同一介面、多種形態
兩種多態形式
- 繼承多態 (Subtyping):子類覆寫虛擬方法,透過基類參考呼叫衍生實現
- 介面多態:實作同一介面的不同類別,透過介面參考互換使用
經典範例:形狀集合統一處理
var shapes = new List<Shape>
{
new Circle(10),
new Rectangle(5, 8),
new Circle(3.5)
};
foreach (var shape in shapes)
{
// 編譯期綁定 Shape.PrintInfo,執行期動態分派至具體 CalculateArea/Perimeter
shape.PrintInfo();
// 輸出:
// Circle: Area=314.16, Perimeter=62.83
// Rectangle: Area=40.00, Perimeter=26.00
// Circle: Area=38.48, Perimeter=21.99
}
多態的三大前提
- 繼承/實作關係:衍生自共同基類或介面
- 方法覆寫:
virtual/abstract + override (或介面隱含虛擬)
- 基類/介面參考:變數宣告型別為基類或介面
四、綜合實戰:可擴充的圖形編輯器核心
// 1. 介面定義契約 (比抽象類別更鬆耦合)
public interface IShape
{
string Name { get; }
double Area { get; }
double Perimeter { get; }
void Render(ICanvas canvas);
bool Contains(Point p); // 擊測
}
// 2. 抽象基類提供共享實現 (模板方法、公用邏輯)
public abstract class ShapeBase : IShape
{
public abstract string Name { get; }
public abstract double Area { get; }
public abstract double Perimeter { get; }
public virtual void Render(ICanvas canvas)
=> canvas.DrawPath(GetPath()); // 統一委派給 GetPath
public abstract GraphicsPath GetPath(); // 子類提供幾何路徑
public virtual bool Contains(Point p) => GetPath().IsVisible(p);
}
// 3. 具體形狀
public class EllipseShape : ShapeBase
{
public RectangleF Bounds { get; set; }
public override string Name => "Ellipse";
public override double Area => Math.PI * Bounds.Width/2 * Bounds.Height/2;
public override double Perimeter => Math.PI * (3*(Bounds.Width/2 + Bounds.Height/2) - Math.Sqrt((3*Bounds.Width/2 + Bounds.Height/2)*(Bounds.Width/2 + 3*Bounds.Height/2)));
public override GraphicsPath GetPath()
{
var path = new GraphicsPath();
path.AddEllipse(Bounds);
return path;
}
}
// 4. 擴充新形狀完全不動既有代碼 (開放封閉原則)
public class TriangleShape : ShapeBase
{
public PointF P1, P2, P3;
public override string Name => "Triangle";
public override double Area => Math.Abs((P1.X*(P2.Y-P3.Y) + P2.X*(P3.Y-P1.Y) + P3.X*(P1.Y-P2.Y))/2);
public override double Perimeter => Distance(P1,P2)+Distance(P2,P3)+Distance(P3,P1);
public override GraphicsPath GetPath()
{
var path = new GraphicsPath();
path.AddPolygon(new[]{P1,P2,P3});
return path;
}
private double Distance(PointF a, PointF b) => Math.Sqrt((a.X-b.X)*(a.X-b.X)+(a.Y-b.Y)*(a.Y-b.Y));
}
// 5. 畫布介面 (依賴倒置)
public interface ICanvas
{
void DrawPath(GraphicsPath path, Pen pen = null, Brush brush = null);
}
// 6. 使用端:完全面向介面程式設計
public class DrawingEditor
{
private readonly List<IShape> _shapes = new();
private readonly ICanvas _canvas;
public DrawingEditor(ICanvas canvas) => _canvas = canvas;
public void AddShape(IShape shape) => _shapes.Add(shape);
public void RenderAll()
{
foreach (var shape in _shapes)
shape.Render(_canvas); // 多態分派
}
public IShape HitTest(Point p) => _shapes.LastOrDefault(s => s.Contains(p));
}
五、SOLID 原則與三大特性的映射關係
| SOLID | 對應特性 | 實踐要點 |
| S 單一職責 | 封裝 | 類別只負責一個業務邏輯,欄位/方法高內聚 |
| O 開放封閉 | 繼承 + 多態 | 擴充靠衍生新類,修改靠抽象介面,既有代碼不動 |
| L 里氏替換 | 繼承 + 多態 | 子類必須能無縫替換基類,前置條件不強化、後置條件不弱化 |
| I 介面隔離 | 多態 (介面) | 介面細粒度、專一,避免胖介面強迫實作不需要的方法 |
| D 依賴倒置 | 多態 | 高層模組依賴抽象 (介面/抽象類),不依賴具體實現 |
六、總結
- 封裝劃定邊界,守護不變量 →
private + property + 驗證邏輯
- 繼承建立分類體系,複用與擴充 →
abstract/virtual + override + 模板方法
- 多態實現同一介面多種行為 → 基類參考 + 虛擬方法分派 + 介面依賴
三者相輔相成:封裝是基石,繼承是骨架,多態是靈魂。熟練運用它們配合 SOLID 原則,才能寫出「易讀、易改、易測、易擴」的高品質 C# 代碼。