B3.1 單一類別的 OOP
單一類別的物件導向程式設計(Fundamentals of OOP for a single class)說明如何設計一個類別:定義屬性與方法、用建構子建立物件、用封裝保護資料,以及靜態成員與 UML 類別圖。這是 SL 與 HL 都要掌握的 OOP 基礎。
類別和物件有什麼關係?
- 類別(class):物件的藍圖或模板,定義物件有哪些資料與行為。例如「學生」。
- 物件(object):依類別建立的實體(instance),各自有自己的資料。例如「Amy」「Ben」兩位學生。
- 屬性(attribute):物件的資料,例如姓名、年級。
- 方法(method):物件能做的動作,例如加入成績、計算平均。
- 建構子(constructor):建立物件時自動執行的特殊方法,用來設定初始值。
如何定義一個類別?
class Student:
school = "IB Academy" # 類別(靜態)屬性:所有物件共用
count = 0
def __init__(self, name, level): # 建構子
self.__name = name # 私有屬性(名稱前加 __)
self.__level = level
self.__scores = []
Student.count += 1
# getter
def get_name(self):
return self.__name
# setter:在修改前檢查資料
def set_level(self, level):
if level in ("SL", "HL"):
self.__level = level
else:
raise ValueError("level 必須是 SL 或 HL")
def add_score(self, score):
if 1 <= score <= 7:
self.__scores.append(score)
def average(self):
if not self.__scores:
return 0
return sum(self.__scores) / len(self.__scores)
amy = Student("Amy", "HL")
amy.add_score(6)
amy.add_score(7)
print(amy.get_name(), amy.average()) # Amy 6.5
print(Student.count) # 1
import java.util.ArrayList;
public class Student {
private static int count = 0; // 靜態屬性
private String name; // 私有屬性
private String level;
private ArrayList<Integer> scores = new ArrayList<>();
public Student(String name, String level) { // 建構子
this.name = name;
this.level = level;
count++;
}
public String getName() { // getter
return name;
}
public void setLevel(String level) { // setter
if (level.equals("SL") || level.equals("HL")) {
this.level = level;
} else {
throw new IllegalArgumentException("level 必須是 SL 或 HL");
}
}
public void addScore(int score) {
if (score >= 1 && score <= 7) {
scores.add(score);
}
}
public double average() {
if (scores.isEmpty()) return 0;
int sum = 0;
for (int s : scores) sum += s;
return (double) sum / scores.size();
}
public static int getCount() {
return count;
}
}
什麼是封裝?
封裝(encapsulation) 把屬性設為私有(private),外部只能透過公開(public)方法存取。好處:
- 保護資料:setter 可以先驗證,例如分數只接受 1–7,避免物件進入不合理狀態。
- 隱藏實作:外部不需要知道分數用串列還是陣列存,日後修改內部實作也不會影響使用者。
| 存取修飾詞 | Java | Python 慣例 | 誰能存取 |
|---|---|---|---|
| public | public |
一般名稱 | 任何地方 |
| private | private |
名稱前加 __ |
只有類別內部 |
| protected(HL 繼承時用) | protected |
名稱前加 _ |
類別內部與子類別 |
靜態成員和實例成員有什麼不同?
- 實例(instance)成員:每個物件各有一份,例如每位學生的
name。 - 靜態(static)成員:屬於類別本身,所有物件共用一份,例如
count記錄已建立幾個學生。呼叫時用類別名稱,例如Student.getCount()。
如何畫 UML 類別圖?
UML(Unified Modeling Language)類別圖用三格方框表示一個類別:類別名稱、屬性、方法。- 表示 private,+ 表示 public,底線表示靜態。
classDiagram
class Student {
-String name
-String level
-List~int~ scores
-int count$
+Student(name, level)
+getName() String
+setLevel(level) void
+addScore(score) void
+average() double
+getCount()$ int
}
推薦影音
Python OOP:類別與實例(Corey Schafer)
簡述:Corey Schafer 系列第一集,示範如何定義類別、__init__ 建構子、實例屬性與方法,以及 self 的意義,非常適合 Python 學生。
物件導向程式設計簡介
簡述:Programming with Mosh 說明 OOP 四大支柱,SL 學生先專注在「封裝」與「抽象」兩部分即可。
練習題
練習 1:名詞辨認
在 amy = Student("Amy", "HL") 中,哪個是類別、哪個是物件、哪個是傳給建構子的引數?
參考答案
Student 是類別;amy 是物件(實例);"Amy" 與 "HL" 是傳給建構子的引數。
練習 2:設計類別
設計一個 BankAccount 類別:私有屬性 owner、balance;方法 deposit(amount)、withdraw(amount)(餘額不足時不允許提款並回傳 False)、get_balance()。
參考答案
class BankAccount:
def __init__(self, owner, balance=0):
self.__owner = owner
self.__balance = balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
return True
return False
def get_balance(self):
return self.__balance
練習 3:封裝的理由
為什麼 BankAccount 的 balance 應設為私有,而不是讓外部直接寫 account.balance = 1000000?
參考答案
私有屬性加上方法控制,可以確保餘額只能透過存款、提款等合法操作改變,並在方法中驗證金額與記錄交易。若外部能直接改值,任何程式碼都能把餘額改成任意數字或負數,資料就失去正確性。