物件導向:封裝、繼承與多型的責任邊界
本頁為依教材與考古題整理的原創摘要;考古題答案經技術覆核,但不是官方答案。
第一次接觸也沒關係
這堂先懂這些詞
先記住白話意思,不必急著背英文。看到正文時,再把正式名稱接回來。
動態派發
也會看到:dynamic dispatch、polymorphism、interface程式透過共同介面呼叫方法,執行時再依實際物件選擇對應版本。
- 生活例子:
- 同一個『付款』按鈕,刷卡與行動支付各自執行不同流程。
- 別搞混:
- 多型不只是同名函式;核心是可透過共同型別使用不同實作。
物件導向三大特性
也會看到:OOP、encapsulation、inheritance、polymorphism、封裝、繼承、多型用封裝隱藏內部、繼承重用關係、多型讓同一介面有不同實作。
- 生活例子:
- 各種付款工具都用「付款」按鈕,但信用卡與轉帳各自執行不同流程。
- 別搞混:
- 三者是設計工具,不是使用 class 就自然得到好設計。
物件狀態與變數範圍
也會看到:instance variable、class variable、local variable、scopeInstance variable 屬於個別物件,class variable 由類別共享,local variable 只在局部程式區塊可見。
- 生活例子:
- 像每位會員各有餘額、全店共享稅率、收銀員當次計算有暫存小計。
- 別搞混:
- 共享範圍與生命週期依語言規則而定,不要把 local variable 當物件永久狀態。
以 class、object 與對外介面建立 OOP 基礎,區分 encapsulation、inheritance、polymorphism、instance/class variables 與 access control。存取控制與繼承細節以本課來源涵蓋的 Java 語意為準,避免把特定語言規則誤當成所有 OOP 語言的共同定律。對應考古題答案均為非官方技術覆核。
先抓住這幾件事
- 說明 class、object、state、behavior 與 encapsulation 的關係。
- 區分 instance variable、class variable 與 local variable 的生命週期與共享範圍。
- 分析 inheritance、abstract class 與 access modifier 的語言相依規則。
- 使用共同介面與動態派發解釋 polymorphism。
先想像這個場景
連鎖飲料店的點單系統
總公司要讓不同分店都能販售飲料,但不能讓任何收銀員直接把庫存或餘額改成非法值。每一杯飲料有自己的甜度與容量,所有杯子又共享品牌規則;結帳畫面只想呼叫同一個 prepare(),不想為紅茶、拿鐵逐一寫 if。這正好把 class、object、encapsulation、共享狀態與 polymorphism 放進同一個責任邊界。
先別急著往下看,花十秒想一想:
系統規定庫存不能小於 0。你會讓任何程式直接修改 stock,還是只開放會檢查數量的 sell() 與 restock()?哪種方式較能守住規則?
把故事換成電腦語言
| 生活中的角色 | 對應到 | 技術概念 |
|---|---|---|
| 『飲料』規格描述容量、甜度與 prepare();今天做出的 A 杯、B 杯各有實際狀態 | Class、object、state、behavior | |
| 庫存只能透過 sell()、restock() 修改,methods 會拒絕負數或超賣 | Encapsulation 與 invariant | |
| 每杯的甜度各自保存;全店共用 createdCount;一次結帳中的 change 只在該 method 執行期間存在 | Instance、class/static、local variables | |
| 抽象 Drink 提供共同概念,Tea 與 Latte 擴充它;Java 系統再用 access modifiers 控制可見性 | Inheritance、abstract class 與 access control | |
| 收銀畫面持有 Drink 介面,只呼叫 prepare();實際杯子是 Tea 或 Latte 時執行各自 override | Subtype polymorphism 與 dynamic dispatch |
題目出現這些字,先想到
- 題目混用 class、object、state、behavior:Class 是描述;object 是 instance;fields 表 state,methods 表 behavior。
- 題目問 member、instance、static/class 或 local variable:Instance 每物件一份;static/class 由 class 共享;local 在 method/block scope,不是持久 object state。
- 看到 subclass 一定能存取所有 superclass members,或 protected 適用所有語言:否定全稱;private、constructor 與 protected 規則都需依目標語言,本課採 Java 語意。
- 題目問 polymorphism 的目的或執行哪個 override:共同 interface/base type 提供契約,runtime object type 透過 dynamic dispatch 選具體實作;不等於 multiple inheritance。
1.Class、Object 與 Encapsulation
物件導向程式設計(OOP)把程式組織成 object(物件)的集合,每個 object 包含 data(資料,稱為 attribute/field)和 behavior(行為,稱為 method)。Class(類別)是 object 的藍圖(blueprint),定義了這類 object 有哪些 attribute 和 method。Object 是 class 的 instance(實例)。 【具體範例】 class Dog { String name; // attribute int age; // attribute void bark() { ... } // method } Dog myDog = new Dog(); // myDog 是 Dog class 的 instance(object) myDog.name = "小黑"; myDog.bark(); 【Encapsulation(封裝)】把 data 和操作 data 的 method 包裝在一起,並透過 access modifier 控制外部的存取權限。目的是 information hiding — 外部只能透過公開的 method(public interface)和 object 互動,不能直接存取內部的 data。 class BankAccount { private double balance; // 外部不能直接存取 public void deposit(double amt) { // 只能透過方法操作 if (amt > 0) balance += amt; } public double getBalance() { // 提供受控的讀取 return balance; } } 封裝的好處:(1) 保護 data 完整性(不會有人直接把 balance 設成負數)。(2) 可以改變內部實作而不影響外部使用者。(3) 降低耦合度。 【Constructor(建構子)】特殊的 method,在建立 object 時自動呼叫,用來初始化 attribute。名稱和 class 相同,沒有 return type。 【考試連結】(1) Class 是藍圖,Object 是實例 — 不要搞混。(2) Encapsulation 的核心是 information hiding + access control。(3) Private 表示只有 class 內部能存取,public 表示所有地方都能存取。
- State 常由 fields/member variables 表示,behavior 常由 methods/member functions 表示。
- Public interface 說明外部可做什麼;implementation details 說明內部如何完成。
- 資料隱藏是 encapsulation 的常見手段,但目的在維護責任與 invariant。
- Abstraction 強調保留重要特徵;encapsulation 強調把狀態與操作包在受控邊界內。
2.Instance、Class 與 Local Variables
在 OOP 中,變數(variable)依據宣告位置和生命週期分為三類: 【Instance Variable(實例變數)】 宣告在 class 內但 method 外。每個 object 有自己獨立的一份副本。生命週期和 object 相同。 class Student { String name; // instance variable — 每個學生有自己的名字 int grade; } Student s1 = new Student(); s1.name = "Alice"; Student s2 = new Student(); s2.name = "Bob"; // s1.name 和 s2.name 是不同的副本 【Class Variable(類別變數 / Static Variable)】 用 static 修飾。所有 object 共享同一份。屬於 class 本身,不屬於任何特定 object。可以用 class 名稱直接存取(如 Student.count),不需要建立 object。 class Student { static int count = 0; // class variable — 所有學生共享 Student() { count++; } // 每建立一個學生,count +1 } 【Local Variable(區域變數)】 宣告在 method 或 block 內。只在該 method/block 執行期間存在。Method 結束後就消失。必須在使用前初始化(instance variable 有預設值,但 local variable 沒有)。 void calculate() { int result = 0; // local variable result = 10 + 20; // method 結束後 result 消失 } 【記憶體配置】 • Instance variables → 存在 heap 上(和 object 一起) • Class variables → 存在 method area / static area • Local variables → 存在 stack 上(和 method 的 stack frame 一起) 【考試連結】(1) 區分 instance variable、class variable、local variable 的差異(scope、lifetime、memory location)。(2) Static method 只能存取 static variable,不能直接存取 instance variable(因為沒有 this/object)。(3) 預設值:instance variable 有(int=0, String=null),local variable 沒有。
- 兩個 objects 可有不同 instance state。
- Class/static state 的更新會被所有透過同一 class state 的 instances 看見。
- Member accessibility 受 language、modifier 與呼叫位置影響,不能一概說所有 methods 都能跨 class 存取。
- Local variable 通常不屬於 object 的持久 state。
3.Inheritance、Abstract Class 與 Access Control
Inheritance(繼承)讓一個 class(子類 / subclass / derived class)繼承另一個 class(父類 / superclass / base class)的 attribute 和 method,實現 code reuse 和 is-a 關係。 class Animal { String name; void eat() { System.out.println(name + " is eating"); } } class Dog extends Animal { void bark() { System.out.println("Woof!"); } } // Dog 繼承了 Animal 的 name 和 eat(),同時有自己的 bark() 【Method Overriding(方法覆寫)】子類可以重新定義父類的 method,提供不同的實作。Runtime 時根據 object 的實際類型決定呼叫哪個版本(這就是 polymorphism 的基礎)。 【Abstract Class(抽象類別)】宣告了 abstract method 的 class — 這些 method 只有簽名沒有實作,強制子類必須提供實作。Abstract class 不能被 instantiate(不能建立 object)。 abstract class Shape { abstract double area(); // 沒有實作 void describe() { System.out.println("I am a shape"); } } class Circle extends Shape { double radius; double area() { return Math.PI * radius * radius; } // 必須實作 } 【Interface(介面)】定義一組 method 簽名(Java 8+ 可有 default implementation),class 透過 implements 實現。一個 class 可以 implement 多個 interface(Java 不支援 multiple inheritance of classes,但支援 multiple implementation of interfaces)。 【Access Modifiers】 • public:所有地方都能存取 • protected:同 package + 子類 • default(沒寫):同 package • private:只有同 class 【考試連結】(1) Abstract class vs interface 的差異。(2) Overriding(runtime, 子類重新實作父類方法)vs Overloading(compile-time, 同 class 中方法名相同但參數不同)。(3) Java 不支援 class 的多重繼承。
- 在 Java 中,protected 允許 same-package access,並在符合規格條件時供 subclass 存取。
- Private member 的資料可能仍構成 object state,但 subclass 通常不能直接以名稱存取。
- Java class 不支援多重 class inheritance,但可實作多個 interfaces;這是 Java 的語言規則,不是 OOP 的普遍定義。
- 優先確認 is-a 關係;只為重用程式碼而繼承常造成不合理耦合。
4.Polymorphism 是共同介面,不是單純複製程式碼
Polymorphism(多型)是 OOP 的核心概念 — 同一個介面(method name)在不同的 object 上有不同的行為。「一個介面,多種實作」。 【Runtime Polymorphism(動態多型)】透過 inheritance + method overriding 實現。在執行時根據 object 的實際類型決定呼叫哪個 method 版本。也叫做 dynamic dispatch 或 late binding。 Animal a1 = new Dog(); // 宣告類型 Animal,實際類型 Dog Animal a2 = new Cat(); // 宣告類型 Animal,實際類型 Cat a1.makeSound(); // 呼叫 Dog 的 makeSound() → "Woof!" a2.makeSound(); // 呼叫 Cat 的 makeSound() → "Meow!" // 同樣的 makeSound() 呼叫,根據 object 實際類型產生不同行為 這是 OOP 最強大的特性 — 你可以寫一個方法接受 Animal 類型的參數,它能正確處理任何 Animal 的子類,不需要知道具體是 Dog 還是 Cat。新增 Bird class 時,現有的程式碼完全不需要修改。 【Compile-time Polymorphism(靜態多型)】透過 method overloading 實現。同一個 class 中有多個同名方法,但參數類型或數量不同。Compiler 在編譯時根據參數決定呼叫哪個版本。 void print(int x) { ... } // print(int) void print(String s) { ... } // print(String) void print(int x, int y) { ... } // print(int, int) 【多型的好處】 (1) 程式碼可擴展 — 新增子類不需要修改現有程式碼(Open-Closed Principle)。 (2) 降低耦合 — 呼叫端只需要知道介面,不需要知道具體實作。 (3) 可替換性 — 可以用不同的實作替換,只要遵循相同的介面。 【考試連結】(1) Runtime polymorphism 靠 inheritance + overriding;compile-time polymorphism 靠 overloading。(2) Dynamic binding 在執行時決定呼叫哪個 method,static binding 在編譯時決定。(3) Polymorphism 不是「複製一份程式碼改改名」— 那是 code duplication,沒有用到任何 OOP 特性。
- 相同 method call 可由不同 object 執行不同實作。
- Interface/base type 定義契約,concrete class 實作契約。
- Code reuse 也可透過 composition、delegation 或 generic functions,不只 inheritance。
- Overloading 與 overriding 都可能被稱為 polymorphism,但規則與 dispatch 時機不同。
一起拆題目
範例 1:BankAccount 必須禁止餘額變成負數。為何把 balance 設為 private,並只提供 deposit(amount) 與 withdraw(amount),比公開 balance 更能維持 invariant?
- 先定義 invariant:balance 不得小於 0。
- 若 balance 公開,外部可直接寫入 -100,class 無法攔截。
- deposit 可拒絕非正數;withdraw 可先檢查餘額是否足夠。
- 所有狀態改變經受控 methods,class 才能集中維護規則。
所以答案是:Encapsulation 讓外部依賴受控介面而非直接改 state;private 本身是手段,真正價值是 methods 能維持 balance invariant。
範例 2:Counter class 的 instance variable value 初值為 0,class/static variable createdCount 初值為 0;constructor 每建立一個 instance 就執行 createdCount++。建立 A、B 後再將 A.value 加 1,三個值各是多少?
- A 與 B 各有自己的 value copy。
- 只增加 A.value,不改變 B.value。
- createdCount 屬於 class,每建立一個 instance 時共同累加。
- 建立 A、B 後,兩者觀察到的是同一份 createdCount=2。
所以答案是:A.value 與 B.value 分離;createdCount 共用。操作後 A.value=1、B.value=0、createdCount=2。
範例 3:Shape 定義 area(),Circle 與 Rectangle 各自 override。函式 totalArea(shapes) 只呼叫每個元素的 area(),為何不需判斷具體 class?
- Shape 提供共同 area() 契約。
- Circle 與 Rectangle 各自實作計算公式。
- 集合以共同 base/interface type 保存 objects。
- 呼叫 area() 時由 dynamic dispatch 選擇實際 object 的 override。
所以答案是:這是 subtype polymorphism:呼叫端依賴 Shape 契約,runtime 選出 Circle 或 Rectangle 的實作。
範例 4:某敘述說『protected 在所有 OOP 語言中都只允許目前 class 與 subclasses 存取』。如何判斷?
- 先辨認 access modifier 是 language-specific semantics。
- 許多語言確實讓 subclasses 存取 protected members。
- Java protected 也允許 same-package access;其他語言的精確規則必須另查其規格。
- 因此含『所有語言』『只』的全稱敘述不成立。
所以答案是:不能跨語言一概而論;protected 的精確可見性必須依目標語言規格判斷。
這裡最容易選錯
- 把 encapsulation 與 abstraction 當成完全相同概念。
- 把 member variable 說成必須宣告在 method body;那通常是 local variable。
- 認為 subclass 一定能直接存取 superclass 的所有 attributes 與 methods。
- 把 protected、private 或 multiple inheritance 的某一語言規則套到所有 OOP 語言。
- 把 polymorphism 的主要目的說成 multiple inheritance 或單純 code reuse。
- 只因兩個 classes 有共同程式碼就建立 inheritance,忽略 is-a 關係與 composition。
換你快速判斷
先在心中作答,再展開答案。答不出來時,回頭找本課的對照關係。
1Encapsulation 的核心目的為何?
把 state 與操作 state 的 behavior 放進受控邊界,讓外部透過能維持 invariant 的介面互動。
Private fields 是常見手段,但真正目的不是單純隱藏,而是維護責任與有效狀態。
2Instance variable、class/static variable、local variable 如何區分?
Instance variable 每個 object 各有一份;class/static variable 由 class 層級共享;local variable 只存在 method/block 執行範圍。
Member variable 宣告在 class 內;宣告於 method body 的通常是 local variable。
3Protected 是否在所有語言都代表『只有 class 與 subclasses 可存取』?
不是;protected 的精確規則依語言而異,例如 Java 還包含 same-package access。
考題若出現『sometimes same-package』通常是在描述 Java 類型的 protected semantics。
4Subclass 是否一定能直接存取 superclass 的所有 members?
不一定;private members、constructors 與語言規則都可能限制繼承或直接存取。
Inheritance 建立 is-a 關係與可重用契約,不等於取消 access control。
5Polymorphism 的主要價值是什麼?
讓呼叫端透過共同 interface 使用不同 implementations,而不需知道每個 concrete class。
Dynamic dispatch 會依 runtime object 選擇 override;這不同於只為 code reuse 而 inheritance。
6Multiple inheritance 是所有 OOP 語言的共同能力嗎?
不是;Java class 不支援多重 class inheritance,但可實作多個 interfaces。
Multiple inheritance 是語言設計選擇,不是 OOP 的必要條件。本課只主張來源可驗證的 Java 規則。
最後用考古題驗證
本課連結的題目都已通過可重現的技術覆核,可逐題練習與判分。
開始本課考古題練習參考來源
- Object-Oriented Programming Concepts — Oracle Java Tutorials