B3.2 多個類別的 OOP(HL)
多個類別的物件導向程式設計(Fundamentals of OOP for multiple classes)說明類別之間如何建立關係:用繼承重用程式碼、用多型讓同一個方法呼叫有不同行為、用抽象類別定義共同介面,以及用組合與聚合把物件組合成更大的系統。本子題只有 HL 需要學,最後也介紹幾個常見的設計模式。
HL 專屬子題
B3.2 整個子題只出現在 HL 考卷,SL 學生可略過本頁。
繼承如何重用程式碼?
繼承(inheritance) 讓 子類別(subclass) 自動擁有 父類別(superclass) 的屬性與方法,再加入或修改自己的部分。表示「is-a」關係:學生 是一種 人。
class Person:
def __init__(self, name):
self._name = name # protected
def introduce(self):
return f"I am {self._name}."
class Student(Person):
def __init__(self, name, level):
super().__init__(name) # 呼叫父類別建構子
self._level = level
def introduce(self): # 覆寫(override)
return f"I am {self._name}, an IB CS {self._level} student."
class Teacher(Person):
def __init__(self, name, subject):
super().__init__(name)
self._subject = subject
def introduce(self):
return f"I am {self._name} and I teach {self._subject}."
public class Person {
protected String name;
public Person(String name) { this.name = name; }
public String introduce() { return "I am " + name + "."; }
}
public class Student extends Person {
private String level;
public Student(String name, String level) {
super(name);
this.level = level;
}
@Override
public String introduce() {
return "I am " + name + ", an IB CS " + level + " student.";
}
}
多型是什麼?
多型(polymorphism) 指同一個方法呼叫,依物件實際的類別執行不同的實作:
people = [Student("Amy", "HL"), Teacher("Mr. Lee", "CS"), Person("Guest")]
for p in people:
print(p.introduce()) # 各自呼叫自己類別的 introduce()
- 覆寫(overriding):子類別重新定義父類別的同名方法,屬於執行期多型。
- 多載(overloading):同一個類別中,同名方法有不同參數列,例如 Java 的
add(int a, int b)與add(double a, double b)。Python 不直接支援多載,通常用預設參數取代。
多型的好處是:新增一種 Person 子類別時,上面的迴圈完全不用改。
抽象類別與介面
抽象類別(abstract class) 不能直接建立物件,只用來定義子類別必須實作的方法,確保所有子類別都有共同介面。Java 另外有 介面(interface),一個類別可以實作多個介面。
from abc import ABC, abstractmethod
import math
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return math.pi * self.r ** 2
class Rectangle(Shape):
def __init__(self, w, h):
self.w, self.h = w, h
def area(self):
return self.w * self.h
組合與聚合有什麼不同?
兩者都是「has-a」關係,差別在 生命週期:
- 組合(composition):部分不能脫離整體而存在。房子被拆了,房間也不存在。
- 聚合(aggregation):部分可以獨立存在。班級解散了,學生仍然存在。
class Course:
def __init__(self, title):
self.title = title
self.students = [] # 聚合:學生物件由外部建立、可獨立存在
self.syllabus = Syllabus(title) # 組合:課綱隨課程建立與消滅
def enrol(self, student):
self.students.append(student)
優先使用組合而非繼承
繼承讓子類別和父類別緊密綁在一起,父類別一改,所有子類別都受影響。若關係是「has-a」而不是「is-a」,用組合會更有彈性。
UML 類別圖中的關係
classDiagram
Person <|-- Student
Person <|-- Teacher
Course o-- Student : 聚合
Course *-- Syllabus : 組合
Teacher --> Course : 教授
class Person {
#String name
+introduce() String
}
class Student {
-String level
+introduce() String
}
class Teacher {
-String subject
+introduce() String
}
class Course {
-String title
+enrol(Student) void
}
class Syllabus
空心三角形箭頭表示繼承、空心菱形表示聚合、實心菱形表示組合,# 表示 protected。
常見設計模式
設計模式(design pattern) 是常見設計問題的經典解法:
| 模式 | 解決的問題 | 例子 |
|---|---|---|
| 單例(Singleton) | 整個程式只能有一個實例 | 資料庫連線管理、設定檔 |
| 工廠(Factory) | 依條件建立不同子類別物件,呼叫者不必知道細節 | ShapeFactory.create("circle") |
| 觀察者(Observer) | 一個物件狀態改變時,自動通知多個訂閱者 | 成績公布時通知學生與家長 App |
推薦影音
Python OOP:繼承與子類別(Corey Schafer)
簡述:示範如何用 super() 呼叫父類別建構子、覆寫方法,以及 isinstance()、issubclass() 的用法,對應本頁繼承一節。
Java 程式設計入門(freeCodeCamp)
簡述:freeCodeCamp 的 Java 入門課程後半段講解類別、物件、繼承與多型,適合學校使用 Java 的 HL 學生搭配本頁練習。
練習題
練習 1:is-a 或 has-a
判斷下列關係應用繼承還是組合/聚合:(a) Car 與 Engine;(b) ElectricCar 與 Car;(c) Library 與 Book。
參考答案
(a) 組合,汽車「有」引擎;(b) 繼承,電動車「是一種」汽車;(c) 聚合,圖書館「有」書,書可以獨立存在(被移到另一間圖書館)。
練習 2:多型
建立抽象類別 Employee,有抽象方法 monthly_pay()。子類別 FullTime(月薪固定)與 PartTime(時薪 × 時數)。寫一段程式計算所有員工的薪資總和。
參考答案
from abc import ABC, abstractmethod
class Employee(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def monthly_pay(self):
pass
class FullTime(Employee):
def __init__(self, name, salary):
super().__init__(name)
self.salary = salary
def monthly_pay(self):
return self.salary
class PartTime(Employee):
def __init__(self, name, rate, hours):
super().__init__(name)
self.rate, self.hours = rate, hours
def monthly_pay(self):
return self.rate * self.hours
staff = [FullTime("Amy", 50000), PartTime("Ben", 200, 60)]
print(sum(e.monthly_pay() for e in staff)) # 62000
練習 3:覆寫與多載
說明覆寫(overriding)與多載(overloading)的差別,各舉一例。
參考答案
覆寫:子類別重新定義父類別的同名同參數方法,例如 Student.introduce() 覆寫 Person.introduce(),執行時依物件實際類別決定呼叫哪個。多載:同一類別中同名但參數不同的方法,例如 Java 的 area(double r) 與 area(double w, double h),編譯時依參數決定。
練習 4:設計模式
一個學校 App 需要在「成績公布」時,同時推播通知給學生、家長與導師。應使用哪個設計模式?簡述其結構。
參考答案
觀察者模式(Observer)。GradeBook(被觀察者)維護一份訂閱者清單,提供 subscribe() 與 notify();學生、家長、導師各自實作 update() 方法。成績公布時 GradeBook 呼叫 notify(),逐一呼叫每個訂閱者的 update()。新增通知對象時不需要修改 GradeBook。