🏗️ 物件導向程式設計(OOP)
📌 什麼是物件導向?
物件導向就像用積木蓋房子:
- 類別(Class) = 積木的設計圖(藍圖)
- 物件(Object) = 根據設計圖做出的實際積木
- 一張設計圖可以做出很多個積木
📌 類別與物件
// 定義一個「學生」類別(設計圖)
class Student
{
// 屬性(Properties)— 學生有什麼特徵
public string Name { get; set; } = ""; // 姓名(自動屬性)
public int Age { get; set; } // 年齡
public double GPA { get; set; } // 成績
// 建構子(Constructor)— 建立物件時自動執行
public Student(string name, int age, double gpa)
{
Name = name; // 設定姓名
Age = age; // 設定年齡
GPA = gpa; // 設定成績
}
// 無參數建構子
public Student() { } // 什麼都不做,使用預設值
// 方法(Methods)— 學生能做什麼
public void Introduce() // 自我介紹的方法
{
Console.WriteLine($"我是 {Name},{Age} 歲,GPA {GPA}");
}
public bool IsPassing() => GPA >= 2.0; // 是否及格
}
// 建立物件(根據設計圖做出實際的積木)
Student s1 = new Student("小明", 20, 3.5); // 用建構子建立
s1.Introduce(); // 輸出:我是 小明,20 歲,GPA 3.5
Student s2 = new Student // 用物件初始化語法建立
{
Name = "小美", // 設定姓名
Age = 19, // 設定年齡
GPA = 3.8 // 設定成績
};
Console.WriteLine(s2.IsPassing()); // 輸出:True
📌 屬性(Properties)
class Product
{
// 自動屬性(最常用)
public string Name { get; set; } = ""; // 可讀可寫
public decimal Price { get; set; } // 可讀可寫
// 唯讀屬性(只有 get)
public DateTime CreatedAt { get; } = DateTime.Now; // 建立後不能改
// 計算屬性(根據其他屬性動態計算)
public decimal Tax => Price * 0.05m; // 稅金 = 價格 * 5%
public decimal Total => Price + Tax; // 總價 = 價格 + 稅金
// 有驗證邏輯的屬性
private int _stock; // 私有欄位(backing field)
public int Stock // 公開屬性
{
get => _stock; // 取得庫存數量
set // 設定庫存數量(帶驗證)
{
if (value < 0) // 庫存不能是負數
throw new ArgumentException("庫存不能為負數");
_stock = value; // 驗證通過才設定
}
}
}
var product = new Product { Name = "筆電", Price = 30000 }; // 建立商品
Console.WriteLine($"商品:{product.Name}"); // 輸出:商品:筆電
Console.WriteLine($"稅金:{product.Tax}"); // 輸出:稅金:1500
Console.WriteLine($"總價:{product.Total}"); // 輸出:總價:31500
📌 繼承(Inheritance)
// 基底類別(父類別)— 動物
class Animal
{
public string Name { get; set; } = ""; // 動物名字
public int Age { get; set; } // 動物年齡
// virtual 表示子類別可以覆寫這個方法
public virtual void Speak() // 說話(虛擬方法)
{
Console.WriteLine("..."); // 預設沒有聲音
}
public void Eat() // 吃東西(一般方法)
{
Console.WriteLine($"{Name} 正在吃東西");
}
}
// 衍生類別(子類別)— 狗繼承動物
class Dog : Animal // Dog 繼承 Animal(用 : 表示繼承)
{
public string Breed { get; set; } = ""; // 品種(Dog 獨有的屬性)
// override 覆寫父類別的 virtual 方法
public override void Speak() // 狗的叫聲
{
Console.WriteLine($"{Name} 說:汪汪!"); // 狗會汪汪叫
}
public void Fetch() // 撿球(Dog 獨有的方法)
{
Console.WriteLine($"{Name} 去撿球了!");
}
}
// 衍生類別 — 貓繼承動物
class Cat : Animal // Cat 繼承 Animal
{
public override void Speak() // 貓的叫聲
{
Console.WriteLine($"{Name} 說:喵~"); // 貓會喵喵叫
}
}
// 使用
Dog dog = new Dog { Name = "小白", Age = 3, Breed = "柴犬" };
dog.Speak(); // 輸出:小白 說:汪汪!
dog.Eat(); // 輸出:小白 正在吃東西(繼承自 Animal)
dog.Fetch(); // 輸出:小白 去撿球了!
📌 抽象類別與介面
// 抽象類別(Abstract Class)— 不能直接建立物件
abstract class Shape // 形狀(抽象概念)
{
public string Color { get; set; } = "黑色"; // 一般屬性
// 抽象方法:只有宣告,沒有實作(子類別必須實作)
public abstract double GetArea(); // 取得面積
// 一般方法:有完整的實作
public void PrintInfo() // 印出資訊
{
Console.WriteLine($"顏色:{Color},面積:{GetArea()}");
}
}
class Circle : Shape // 圓形繼承形狀
{
public double Radius { get; set; } // 半徑
// 必須實作抽象方法
public override double GetArea() => Math.PI * Radius * Radius; // 圓面積公式
}
class Rectangle : Shape // 矩形繼承形狀
{
public double Width { get; set; } // 寬
public double Height { get; set; } // 高
public override double GetArea() => Width * Height; // 矩形面積公式
}
// 介面(Interface)— 定義「能做什麼」的契約
interface IMovable // 介面名稱慣例以 I 開頭
{
void Move(int x, int y); // 移動(只有宣告,沒有實作)
double Speed { get; set; } // 速度屬性
}
interface IResizable // 可調整大小的介面
{
void Resize(double factor); // 調整大小
}
// 一個類別可以實作多個介面(但只能繼承一個類別)
class GameCharacter : IMovable, IResizable // 遊戲角色
{
public string Name { get; set; } = ""; // 角色名稱
public double Speed { get; set; } = 1.0; // 移動速度
public void Move(int x, int y) // 實作 IMovable 的 Move
{
Console.WriteLine($"{Name} 移動到 ({x}, {y})");
}
public void Resize(double factor) // 實作 IResizable 的 Resize
{
Console.WriteLine($"{Name} 大小變為 {factor} 倍");
}
}
📌 封裝與存取修飾詞
class BankAccount // 銀行帳戶
{
// private — 只有這個類別內部能存取
private decimal _balance; // 餘額(私有,外部不能直接碰)
// public — 任何地方都能存取
public string Owner { get; set; } = ""; // 帳戶持有人
// protected — 這個類別和子類別能存取
protected string AccountNumber { get; set; } = ""; // 帳號
// internal — 同一個專案內能存取
internal DateTime CreatedDate { get; set; } = DateTime.Now;
// 建構子
public BankAccount(string owner, decimal initialBalance)
{
Owner = owner; // 設定持有人
_balance = initialBalance; // 設定初始餘額
}
// 公開方法控制對私有資料的存取
public decimal GetBalance() => _balance; // 查詢餘額
public void Deposit(decimal amount) // 存款
{
if (amount <= 0) // 驗證金額
throw new ArgumentException("存款金額必須大於 0");
_balance += amount; // 增加餘額
Console.WriteLine($"存入 {amount},餘額 {_balance}");
}
public void Withdraw(decimal amount) // 提款
{
if (amount > _balance) // 檢查餘額是否足夠
throw new InvalidOperationException("餘額不足");
_balance -= amount; // 扣除餘額
Console.WriteLine($"提領 {amount},餘額 {_balance}");
}
}
var account = new BankAccount("小明", 1000); // 建立帳戶
account.Deposit(500); // 存入 500 → 餘額 1500
account.Withdraw(200); // 提領 200 → 餘額 1300
// account._balance = 0; // ❌ 編譯錯誤!_balance 是 private
📌 多型(Polymorphism)
// 多型:同一個方法呼叫,不同物件有不同行為
List<Animal> animals = new List<Animal> // 建立動物清單
{
new Dog { Name = "小白" }, // 加入一隻狗
new Cat { Name = "咪咪" }, // 加入一隻貓
new Dog { Name = "大黃" }, // 再加入一隻狗
};
foreach (Animal animal in animals) // 走訪每隻動物
{
animal.Speak(); // 同樣是 Speak(),但每隻動物的叫聲不同!
}
// 輸出:
// 小白 說:汪汪!
// 咪咪 說:喵~
// 大黃 說:汪汪!
// 多型讓你用「基底類別」的變數來操作「衍生類別」的物件
Animal myPet = new Dog { Name = "旺財" }; // Animal 變數裝 Dog 物件
myPet.Speak(); // 輸出:旺財 說:汪汪!(執行的是 Dog 的版本)
// is 和 as 運算子
if (myPet is Dog dogRef) // 檢查 myPet 是否是 Dog,並轉型
{
dogRef.Fetch(); // 可以使用 Dog 獨有的方法
}
🤔 我這樣寫為什麼會錯?
❌ 錯誤:忘記 override
class MyDog : Animal
{
// ❌ 用 new 只是隱藏父類別的方法,不是覆寫
// 多型時不會正確呼叫
public new void Speak() { Console.WriteLine("汪!"); }
}
// 當用 Animal 型別的變數時:
Animal a = new MyDog { Name = "test" };
a.Speak(); // 輸出:...(呼叫的是 Animal 的版本,不是 MyDog 的!)
// ✅ 正確:用 override 覆寫 virtual 方法
class MyDogFixed : Animal
{
public override void Speak() { Console.WriteLine("汪!"); }
}
❌ 錯誤:想建立抽象類別的實例
// ❌ 錯誤:抽象類別不能直接建立物件
// Shape shape = new Shape(); // 編譯錯誤!
// ✅ 正確:建立具體子類別的物件
Shape shape = new Circle { Radius = 5 }; // 用子類別