物件導向程式設計是一種將程式碼組織為自我封裝物件的程式設計典範。
每個物件會結合資料與操作該資料的方法,讓程式碼更模組化、更彈性,也更可重用。
類別 (class) 是建立物件的藍圖,定義其屬性與方法。
物件 (object) 是類別的實例。它同時包含資料 (類別的屬性) 與操作該資料的方法。
封裝 (Encapsulation) 是把資料與相關方法包在類別內,並將其隱藏以保護資料完整性。
外部程式通常透過公開方法存取它們,常見為 getter (讀取屬性) 與 setter (修改屬性)。
繼承 (Inheritance) 允許建立新類別並繼承既有類別的屬性與方法。它可促進程式碼重用,並建立階層關係。
被繼承的類別稱為基底類別 (base class) 或父類別 (superclass);進行繼承的類別稱為衍生類別 (derived class) 或子類別 (subclass)。
多型 (Polymorphism) 讓衍生類別 (子類別) 的物件可被當成其基底類別 (父類別) 來使用,同時仍保有各自獨特行為。
這使得物件可對同一個方法呼叫做出不同反應,特別是在多個類別共享同名方法、但實作不同時。
抽象化 (Abstraction) 重點在於建立簡潔介面,只暴露相關且必要的功能。
物件常會與其他物件建立關係,例如一個物件使用或包含另一個物件;這類關係稱為關聯 (associations)。
以下是 Java 程式碼:
// Abstract base class
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
// Abstract method
public abstract void makeSound();
public void sleep() {
System.out.println(name + " is sleeping.");
}
}
// Derived class
class Lion extends Animal {
public Lion(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println("Roar!");
}
}
// Derived class
class Parrot extends Animal {
private String color;
public Parrot(String name, String color) {
super(name);
this.color = color;
}
public String getColor() {
return color;
}
@Override
public void makeSound() {
System.out.println("Squawk!");
}
}
public class Zoo {
public static void main(String[] args) {
Lion simba = new Lion("Simba");
Parrot rio = new Parrot("Rio", "Blue");
System.out.println(simba.getName() + " is a Lion.");
simba.makeSound();
simba.sleep();
System.out.println(rio.getName() + " is a " + rio.getColor() + " Parrot.");
rio.makeSound();
rio.sleep();
}
}
在這個範例中:
Animal 是類別,simba 與 rio 是物件。Animal 的 name 與 Parrot 的 color 皆以 private 封裝,並提供公開 getter。Lion 與 Parrot 皆繼承自 Animal。Lion 與 Parrot 雖都可被當作 Animal,但各自實作自己的 makeSound()。Animal 內含抽象方法 makeSound(),要求衍生類別提供實作。
程序式 (Procedural) 與物件導向程式設計 (OOP) 是不同的程式設計典範。程序式偏向線性、以任務為中心;OOP 則強調以物件封裝資料與行為。
程序式:函式操作資料。資料常為全域,可能造成衝突。
OOP:資料與函式封裝於物件中。物件透過方法互動,可維持資料完整性與封裝性。
程序式:程式碼常切分為函式;全域資料可能降低可重用性。
OOP:透過類別與物件達成抽象化;封裝有助於建立離散且自含的模組。
struct 模擬部分 OOP 特性,但不原生支援完整 OOP。實務上,像 Python、JavaScript、C# 等現代語言多半支援多種典範,可依任務需求彈性選擇。
以下是 Python 程式碼:
class Animal:
def __init__(self, sound):
self.sound = sound
def make_sound(animal):
print(animal.sound)
dog = Animal("Woof")
cat = Animal("Meow")
make_sound(dog)
make_sound(cat)
以下是 Java 程式碼:
import java.util.ArrayList;
import java.util.List;
abstract class Animal {
public abstract void makeSound();
}
class Dog extends Animal {
public void makeSound() {
System.out.println("Woof");
}
}
class Cat extends Animal {
public void makeSound() {
System.out.println("Meow");
}
}
public class Main {
public static void main(String[] args) {
List<Animal> animals = new ArrayList<>();
animals.add(new Dog());
animals.add(new Cat());
for (Animal animal : animals) {
animal.makeSound();
}
}
}
封裝是物件導向程式設計原則之一,指的是把資料與操作該資料的方法整合在同一個單位,也就是物件。
資訊隱藏 (Information Hiding):封裝會隱藏內部狀態與實作細節。
存取控制 (Access Control):資料僅透過定義好的方法提供給外部 **:getter**(讀取) 與 setter (寫入)。
行為約束 (Constraining Behavior):存取方法可確保資料符合規則 (例如範圍檢查或格式規範)。
安全性:可控管物件資料存取,降低資料毀損或未授權修改風險。
簡潔性:物件可把複雜系統抽象化,提供簡單介面互動。
彈性:封裝促進低耦合,可在不影響外部程式碼下修改或替換內部實作。
類別建構:Java、C# 等現代語言常採「先封裝」設計,使用 public、private、protected 等存取修飾詞。
API 設計:封裝類別與模組可打造更直覺、聚焦的 API;可隱藏內部策略,只暴露所需功能。
測試:資料隱藏可避免測試直接操作內部狀態,確保透過公開介面驗證物件行為。
以下是 Java 程式碼:
public class Car {
private int fuel; // private ensures that fuel can't be accessed directly from outside the class
public Car() {
this.fuel = 100; // Initialize with 100 units of fuel
}
// Getter method for fuel
public int getFuel() {
return fuel;
}
// Setter method for fuel with encapsulation enforcing constraints
public void setFuel(int fuel) {
if (fuel >= 0 && fuel <= 100) {
this.fuel = fuel;
} else {
System.out.println("Invalid fuel amount.");
}
}
public void drive() {
if (fuel > 0) {
fuel--;
System.out.println("Vroom!");
} else {
System.out.println("Out of fuel!");
}
}
public static void main(String[] args) {
Car myCar = new Car();
myCar.drive();
System.out.println("Fuel remaining: " + myCar.getFuel());
myCar.setFuel(120); // This will print "Invalid fuel amount."
}
}
在物件導向程式設計中,多型可讓不同型別的物件透過共同介面被一致地使用。它能抽象化方法細節,提升程式碼彈性與可重用性。
同一個類別中的多個方法可使用相同名稱,但參數不同,因而可以共存。編譯器會依方法簽章選擇對應方法。
以下是 Java 程式碼:
public class Sum {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
子類別會對父類別已定義的方法提供特定實作,等同取代父類別版本。實際呼叫哪個方法,會在程式執行時決定。
以下是 Python 程式碼:
class Animal:
def speak(self):
return "Animal speaks"
class Cat(Animal):
def speak(self):
return "Cat meows"
obj_cat = Cat()
print(obj_cat.speak()) # Output: Cat meows
在基底類別中以 virtual 標記的方法,可在衍生類別覆寫。這可啟用動態派發 (dynamic dispatch),即使透過基底類別參考,也能呼叫正確方法。
以下是 C++ 程式碼:
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() {
cout << "Animal speaks";
}
};
class Cat : public Animal {
public:
void speak() override {
cout << "Cat meows";
}
};
多型常透過動態派發機制,在執行期決定要呼叫哪個具體方法。
這也稱為延遲繫結 (late binding)。此特性可讓通用程式碼處理多種物件型別,提升彈性與適應性。
繼承是物件導向程式設計中的核心概念,讓你能基於既有類別建立新類別。
繼承建立的是 **「is-a」關係 **:衍生類別 (也叫子類別) 會繼承父類別 (基底類別) 的屬性與行為。
單一繼承 (Single Inheritance):一個類別只繼承一個基底類別。Java 中很常見。
例:類別 B 繼承類別 A。
多重繼承 (Multiple Inheritance):一個類別繼承多個基底類別。C++ 支援;Java 常用介面達成類似效果,以避免「菱形問題」等複雜性。
例:類別 C 同時繼承 A 與 B。
多層繼承 (Multilevel Inheritance):一個類別繼承另一個衍生類別。
例:類別 C 繼承 B,而 B 繼承 A。
階層式繼承 (Hierarchical Inheritance):同一個基底類別被多個子類別繼承。
例:類別 B 與 C 都繼承 A。
混合繼承 (Hybrid Inheritance):上述兩種以上繼承型態的組合,使用上可能帶來複雜度。
例:在 C++ 中,一個類別可同時涉及多重繼承與多層繼承。
以下是 Java 程式碼:
// Single Inheritance
class A {
void funcA() {
System.out.println("Function of class A");
}
}
class B extends A { } // Class B inherits from class A
// Multiple Inheritance using interfaces
interface X {
void funcX();
}
interface Y {
void funcY();
}
class Z implements X, Y { // Class Z implements both interface X and interface Y
public void funcX() {
System.out.println("Function of interface X");
}
public void funcY() {
System.out.println("Function of interface Y");
}
}
// Multilevel Inheritance
class A {
void funcA() {
System.out.println("Function of class A");
}
}
class B extends A {
void funcB() {
System.out.println("Function of class B");
}
}
class C extends B { } // Class C inherits from class B, which inherits from class A
// Hierarchical Inheritance
class H {
void funcH() {
System.out.println("Function of class H");
}
}
class I extends H { } // Class I inherits from class H
class J extends H { } // Class J also inherits from class H
// Java does not directly support hybrid inheritance through classes.
// However, you can achieve something similar using interfaces, as shown with multiple inheritance.
在物件導向程式設計 (OOP) 中,抽象化是聚焦於關鍵特徵、隱藏不必要細節的概念。
它能清楚區分高層概念與具體實作,進而提升可重用性、可維護性與安全性。
以下是 Java 程式碼:
// Abstract Class
abstract class Vehicle {
// Abstract method (no body)
public abstract String start();
// Regular method
public String stop() {
return "Vehicle stopped!";
}
}
// Implementing the abstract class
class Car extends Vehicle {
@Override
public String start() {
return "Car started!";
}
}
// Interface
interface Drivable {
// All methods in an interface are implicitly abstract
void drive();
}
// Implementing both the abstract class and the interface
class Bike extends Vehicle implements Drivable {
@Override
public String start() {
return "Bike started!";
}
@Override
public void drive() {
System.out.println("Bike is being driven!");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
System.out.println(car.start()); // Output: Car started!
System.out.println(car.stop()); // Output: Vehicle stopped!
Bike bike = new Bike();
System.out.println(bike.start()); // Output: Bike started!
bike.drive(); // Output: Bike is being driven!
}
}
在物件導向程式設計中,類別是建立物件 (實例) 的藍圖。它在同一個結構下封裝屬性 (資料) 與行為 (方法)。
建立實例後,每個物件都可攜帶各自資料,同時遵循其類別藍圖。
屬性 (Attributes):儲存物件狀態,可有不同資料型別。
方法 (Methods):定義在類別中的函式,用於操作屬性或執行特定行為。
繼承:讓子類別可繼承父類別的屬性與方法。
封裝:把屬性與方法包在類別內,限制直接存取以保護資料。
多型:允許不同類別、但實作同一介面的物件以一致方式使用。
抽象化:隱藏內部細節,提供簡化介面;在類別中通常透過只暴露必要方法達成。
以下是 Python 程式碼:
class Car:
"""A class to represent a car."""
def __init__(self, make, model, year):
"""Initialize car attributes."""
self.make = make
self.model = model
self.year = year
self.fuel = 0
def fill_tank(self, gallons):
"""Add fuel to the tank. Ensure the amount is positive."""
if gallons > 0:
self.fuel += gallons
else:
print("Invalid fuel amount.")
def drive(self, distance=1):
"""Drive the car, consuming fuel based on distance."""
if self.fuel >= distance:
self.fuel -= distance
print(f"Car drove {distance} unit(s). Remaining fuel: {self.fuel} units.")
else:
print("Insufficient fuel.")
# Create a Car object
my_car = Car("Honda", "Civic", 2022)
# Access its attributes and methods
my_car.fill_tank(10)
for _ in range(15):
my_car.drive()
物件是類別的具體實例。它在單一單位中封裝資料 (屬性) 與行為 (方法)。
以下是 Python 程式碼:
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
print("Woof!")
myDog = Dog("Buddy", "Golden Retriever")
在物件導向程式設計 (OOP) 中,存取修飾詞用來定義類別成員的可見性與可存取範圍。
以下是 Java 程式碼:
public class Car {
private String make; // Accessible only within class
protected int year; // Accessible within class and subclasses
double price; // Default visibility: package-private
public void setMake(String make) {
this.make = make;
}
public String getMake() {
return make;
}
protected void startEngine() {
System.out.println("Engine started!");
}
}
資料封裝:透過隱藏類別內部狀態並以方法對外暴露,可確保資料完整性並更好地控管存取。
繼承與抽象化:存取層級會在繼承情境下調節可見性,讓類別可互動同時維持封裝。
過度使用 Getter/Setter 配對:它們雖有助保護資料,但應避免建立沒有附加價值的方法。
不必要地使用 Public 成員:若成員不需要全域存取,建議使用更嚴格的可見性以提升封裝。
優先考慮組合而非繼承:若類別關聯鬆散,透過隱藏細節與元件可讓維護更容易。
預設使用 Private:能封裝就封裝;成員預設應為 private,必要時再放寬可見性。
Public 成員最小化:僅在確實需要全域存取時使用。
可見性一致:在同一類別中盡量維持一致的可見性策略,避免混淆。
以下介紹三種常見的方法多載技巧。
此方式是透過改變方法簽章中的參數數量。
以下是 Java 範例:
public int calculateSum(int a, int b) { // Two parameters
return a + b;
}
public int calculateSum(int a, int b, int c) { // Three parameters
return a + b + c;
}
另一種方式是重排方法中的參數順序,形成不同簽章。
以下是 Java 範例:
public double calculateArea(double length, double width) { // Length, then width
return length * width;
}
public double calculateArea(double radius) { // Just radius for a circle
return Math.PI * radius * radius;
}
你也可以透過定義不同參數型別的方法達成多載。
以下是 Java 範例:
public void printDetails(String name, int age) {
System.out.println("Name: " + name + " , Age: " + age);
}
public void printDetails(int id) {
System.out.println("ID: " + id);
}
public void printDetails(double salary) {
System.out.println("Salary: " + salary);
}
OOP 中的內聚 (Cohesion) 指的是同一類別內的方法與資料彼此關聯的緊密程度。高內聚類別會聚焦於特定任務或職責,因此更容易維護、理解與確保可靠性。
高內聚是理想特性,代表類別中的方法與屬性能以一致方式協作;相對地,低內聚表示類別包含多個且常互不相關的責任,會增加理解與維護難度。
其中,功能內聚最理想,因為它與單一職責原則 (Single Responsibility Principle) 最一致。
以下是 Java 程式碼:
public class FileUtility {
public String readFile(String fileName) {
// Read a file
return content;
}
public void writeToDatabase(String data) {
// Write content to a database
}
public void clearCache() {
// Clear application cache
}
public List<String> parseFile(String content) {
// Parse file content
return parsedData;
}
}
這個 FileUtility 類別屬於低內聚,因為它混合了檔案操作、資料庫寫入與快取管理。
OOP 中的耦合 (Coupling) 描述類別或模組間的相依程度。它決定不同模組或類別彼此連結的緊密程度,並影響系統彈性、可維護性與可測試性。
在軟體設計中通常偏好低耦合 (loose coupling)。
內容耦合 (Content Coupling):最強耦合型態,一個模組直接存取或修改另一模組內部資料。
共用耦合 (Common Coupling):多個模組共享全域資料;共享資源的任何改動都可能影響所有相依模組。
控制耦合 (Control Coupling):一個模組透過控制資訊 (如旗標) 影響另一模組流程。
外部耦合 (External Coupling):類別或模組因外部因素連結,例如設定檔或資料結構 (schema)。
印記 (資料) 耦合 (Stamp/Data Coupling):模組共享資料結構但僅用其中一部分,需了解傳遞資料的結構細節。
訊息耦合 (Message Coupling):最低耦合型態,模組僅透過標準介面 (如方法呼叫或訊息) 溝通。
單一職責原則 (SRP):遵循 SRP 通常會帶來低耦合,因為類別聚焦單一責任,依賴較少。
開放封閉原則 (OCP):強調可擴充、少修改,通常透過介面或抽象類別擴充,可降低緊耦合風險。
里氏替換原則 (LSP):當衍生類別可無副作用替代基底類別時,通常可降低耦合,使模組更獨立。
介面隔離原則 (ISP):主張使用精簡介面而非萬用介面,能自然降低耦合,避免類別依賴不需要的方法。
依賴反轉原則 (DIP):依賴抽象而非具體實作,可促進低耦合,使系統更模組化且更易調整。
建構子是物件導向程式設計中的特殊方法,用於初始化物件。它可確保新建立的物件具備合適初始狀態與必要資源。
void 也不會有。以下是 Java 程式碼:
class Vehicle {
private String vehicleType;
private int wheels;
// Default Constructor
public Vehicle() {
vehicleType = "Car";
wheels = 4;
}
// Parameterized Constructor
public Vehicle(String type, int wheelCount) {
vehicleType = type;
wheels = wheelCount;
}
public void showDetails() {
System.out.println("Type of Vehicle: " + vehicleType);
System.out.println("Number of Wheels: " + wheels);
}
public static void main(String[] args) {
// Calling Default Constructor
Vehicle car = new Vehicle();
car.showDetails();
// Calling Parameterized Constructor
Vehicle bike = new Vehicle("Bike", 2);
bike.showDetails();
}
}
以下是 C++ 程式碼:
#include <iostream>
using namespace std;
class Person {
private:
string name;
int age;
public:
// Parameterized Constructor
Person(string n, int a) {
name = n;
age = a;
}
void displayInfo() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
int main() {
// Calling Parameterized Constructor
Person p1 = Person("Alice", 25);
p1.displayInfo();
return 0;
}
以下是 C# 程式碼:
using System;
class Car {
private string model;
// Parameterized Constructor
public Car(string m) {
model = m;
}
static void Main() {
// Calling Parameterized Constructor
Car c = new Car("Toyota");
Console.WriteLine("Car Model: " + c.model);
}
}
在物件導向程式設計中,解構子 (或類別的終結器) 用於在物件被銷毀前執行清理動作。
物件可透過明確或隱含方式被銷毀:
解構子是特殊方法,會在物件即將離開作用域或被明確銷毀前自動呼叫。其主要目的在釋放資源或執行清理工作,例如關閉檔案、釋放記憶體。
在記憶體管理上,垃圾回收是專門負責自動回收記憶體的機制。在 Java、C# 等語言中,因垃圾回收可靠,通常不一定需要解構子。
Java 曾提供 finalize() 方法,概念類似解構子。如今通常不建議使用,改以 AutoCloseable 介面與 try-with-resources 來更妥善管理資源。
以下是 C++ 程式碼:
class Resource {
public:
Resource() { std::cout << "Resource Acquired\n"; }
~Resource() { std::cout << "Resource Destroyed\n"; }
};
int main() {
Resource r;
} // Destructs r here
除記憶體外,解構子也可管理多種資源,例如:
以下是 C++ 程式碼:
#include <iostream>
#include <fstream>
class FileHandler {
public:
std::ofstream file;
FileHandler(std::string fileName) {
file.open(fileName);
if (file.is_open()) {
std::cout << "File Opened\n";
}
}
~FileHandler() {
if (file.is_open()) {
file.close();
std::cout << "File Closed\n";
}
}
};
int main() {
FileHandler fh("sample.txt");
} // Destructor called on fh, closes the file
繼承、mixin 與 組合 (composition) 都是物件導向程式設計中與程式碼重用、物件/類別關係相關的技術。
以下分別說明並比較:
Car 是 Vehicle)。Helicopter 可以像 Bird 一樣飛)。Car 擁有 Engine)。許多現代 OOP 設計準則 (例如 composition over inheritance) 都建議:除非有明確「is-a」關係,通常優先使用組合,因為更有彈性,也更容易維護。