這節課將學習 物件導向程式設計(OOP) 的基礎概念,包括 類別、物件、屬性、方法、封裝 以及 繼承。物件導向能讓程式碼更具結構性、可重用性和維護性。未來可以進一步學習「多型」與「抽象類別」等進階概念!
1. 什麼是物件導向程式設計 (OOP)
物件導向是一種程式設計方式,將程式設計視為由物件組成的系統,每個物件都具有自己的 屬性(資料) 和 方法(函式)。
OOP 的三大特性:
- 封裝(Encapsulation):將資料和操作封裝在類別內。
- 繼承(Inheritance):子類別繼承父類別的屬性和方法。
- 多型(Polymorphism):相同的方法可以根據物件的不同表現出不同的行為。
2. 類別與物件
類別(Class)
類別是物件的「模板」,用來定義物件的屬性和方法。
物件(Object)
物件是類別的「實例」,用於存取類別內的屬性和方法。
2.1 定義類別與創建物件
語法:
class 類別名稱 {
// 屬性
public $屬性名稱;
// 方法
public function 方法名稱() {
// 方法的內容
}
}
範例程式碼:定義類別與創建物件
<?php
// 定義一個類別
class Person {
// 屬性(變數)
public $name;
public $age;
// 方法(函式)
public function introduce() {
echo "你好,我叫 $this->name,今年 $this->age 歲。<br>";
}
}
// 創建物件
$person1 = new Person();
$person1->name = "小強";
$person1->age = 30;
// 呼叫方法
$person1->introduce();
$person2 = new Person();
$person2->name = "小華";
$person2->age = 25;
$person2->introduce();
?>
輸出結果:
你好,我叫 小強,今年 30 歲。
你好,我叫 小華,今年 25 歲。
3. 建構子(Constructor)
建構子 是一個特殊的方法,當創建物件時會自動執行。使用 __construct()
方法來定義建構子,通常用來初始化物件的屬性。
範例程式碼:使用建構子初始化物件
<?php
class Person {
public $name;
public $age;
// 建構子
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
// 方法
public function introduce() {
echo "你好,我叫 $this->name,今年 $this->age 歲。<br>";
}
}
// 創建物件並初始化屬性
$person1 = new Person("小強", 30);
$person2 = new Person("小華", 25);
$person1->introduce();
$person2->introduce();
?>
輸出結果:
你好,我叫 小強,今年 30 歲。
你好,我叫 小華,今年 25 歲。
4. 屬性的存取控制:封裝(Encapsulation)
存取控制修飾詞
- public:屬性或方法可以在任何地方被存取。
- private:僅能在類別內部被存取。
- protected:僅能在類別內部和子類別中被存取。
範例程式碼:存取控制
<?php
class Person {
// 屬性存取控制
public $name; // 公開屬性
private $age; // 私有屬性
// 建構子
public function __construct($name, $age) {
$this->name = $name;
$this->setAge($age);
}
// 方法
public function introduce() {
echo "你好,我叫 $this->name,今年 " . $this->getAge() . " 歲。<br>";
}
// 設定年齡(提供存取私有屬性的方法)
public function setAge($age) {
if ($age > 0) {
$this->age = $age;
} else {
$this->age = 0;
}
}
// 取得年齡
public function getAge() {
return $this->age;
}
}
$person = new Person("小強", 30);
$person->introduce();
$person->setAge(35);
$person->introduce();
?>
輸出結果:
你好,我叫 小強,今年 30 歲。
你好,我叫 小華,今年 35 歲。
直接存取 private
屬性會出錯
程式碼:
<?php
class Person {
private $age = 30; // 私有屬性
// 顯示年齡的方法
public function showAge() {
echo "年齡是:$this->age<br>";
}
}
$person = new Person();
// 正確的做法:透過方法存取
$person->showAge();
// 錯誤的做法:直接存取 private 屬性
echo $person->age; // 這行會出錯
?>
執行結果:
年齡是:30
Fatal error: Uncaught Error: Cannot access private property Person::$age
2. 解釋
private
屬性:只能在 類別內部 進行存取,外部直接存取會產生錯誤。- 在上面的例子中,
$age
是private
,所以$person->age
會出現以下錯誤:vbnet複製程式碼Fatal error: Uncaught Error: Cannot access private property Person::$age
5. 繼承(Inheritance)
繼承允許子類別(Child Class)獲取父類別(Parent Class)的屬性和方法,從而達到程式碼重用的目的。
語法:
class 子類別名稱 extends 父類別名稱 {
// 子類別可以添加自己的屬性或方法
}
範例程式碼:繼承
<?php
// 定義父類別
class Animal {
public $name;
public function __construct($name) {
$this->name = $name;
}
public function sound() {
echo "$this->name 發出聲音。<br>";
}
}
// 定義子類別(繼承 Animal)
class Dog extends Animal {
public function sound() {
echo "$this->name 汪汪叫!<br>";
}
}
class Cat extends Animal {
public function sound() {
echo "$this->name 喵喵叫!<br>";
}
}
// 創建物件
$dog = new Dog("小黑");
$cat = new Cat("小花");
$dog->sound();
$cat->sound();
?>
輸出結果:
小黑 汪汪叫!
小花 喵喵叫!
判斷何時使用 private
的關鍵因素
在物件導向程式設計中,private
關鍵字用於將屬性或方法設置為「私有」。主要關鍵因素是 資料的封裝性與安全性。
1. 屬性資料需要保護
如果某個屬性不應該被類別外部直接存取或修改(尤其是敏感資料或內部計算用的值),就應該設為 private
。
這樣可以透過 getter 和 setter 方法來控制存取邏輯,確保屬性不會被賦予不合理的值。
範例:年齡驗證
<?php
class Person {
private $age;
public function setAge($age) {
if ($age > 0 && $age < 150) {
$this->age = $age;
} else {
$this->age = 0; // 無效值設為預設
}
}
public function getAge() {
return $this->age;
}
}
$person = new Person();
$person->setAge(25);
echo "年齡:" . $person->getAge();
?>
2. 不希望外部影響物件內部狀態
某些屬性或方法用來支援類別的內部邏輯,不應該被外部存取或干擾。例如,某些計算結果或輔助方法是類別內部的細節,外部無需得知。
範例:內部日誌記錄(private
方法)
<?php
class Logger {
private function log($message) {
echo "[LOG]: $message<br>";
}
public function performAction() {
$this->log("Action started");
echo "執行主要操作<br>";
$this->log("Action completed");
}
}
$logger = new Logger();
$logger->performAction();
// $logger->log("Test"); // 會報錯,因為 log() 是 private
?>
3. 內部邏輯可能變動
當某個屬性或方法是類別內部邏輯的一部分,但未來可能需要修改或調整,將其設為 private
可以避免外部程式碼依賴這些細節,保護類別的封裝性。
4. 避免不當操作
直接暴露屬性可能會導致資料被外部直接修改,產生不合理或不安全的狀態。設為 private
可以強制使用者透過方法(setter
)進行操作,確保資料正確性。
總結
判斷是否該使用 private
的關鍵因素在於:
- 屬性或方法的存取安全性
- 保護物件的內部狀態
- 避免外部程式碼依賴內部細節
- 確保資料正確性與完整性
將屬性或方法設為 private
,並提供適當的 getter 和 setter 方法,可以有效地封裝物件,讓程式碼更加穩定且容易維護。
原則: 如果某個屬性或方法不需要外部直接存取,預設設為 private
。
這樣能讓類別更具封裝性,避免外部不必要的依賴與干擾。
綜合練習:訂單管理系統
需求:
- 定義一個
Product
類別,表示商品,包含「名稱」和「價格」。 - 定義一個
Order
類別,表示訂單,包含「客戶名稱」和「商品清單」,並能計算總金額。 - 定義一個
DiscountOrder
子類別,繼承Order
,並提供折扣功能。
程式碼範例:
<?php
// 商品類別
class Product {
// 屬性
private $name;
private $price;
// 建構子:初始化商品名稱與價格
public function __construct($name, $price) {
$this->name = $name;
$this->price = $price;
}
// 取得商品名稱
public function getName() {
return $this->name;
}
// 取得商品價格
public function getPrice() {
return $this->price;
}
}
// 訂單類別
class Order {
// 屬性
protected $customerName;
protected $products = []; // 商品清單(陣列)
// 建構子:初始化訂單資訊
public function __construct($customerName) {
$this->customerName = $customerName;
}
// 添加商品到訂單
public function addProduct(Product $product) {
$this->products[] = $product;
}
// 計算總金額
public function getTotal() {
$total = 0;
foreach ($this->products as $product) {
$total += $product->getPrice();
}
return $total;
}
// 顯示訂單資訊
public function displayOrder() {
echo "客戶名稱:" . $this->customerName . "<br>";
echo "商品清單:<br>";
foreach ($this->products as $product) {
echo "- " . $product->getName() . ",價格:$" . $product->getPrice() . "<br>";
}
echo "總金額:$" . $this->getTotal() . "<br>";
}
}
// 折扣訂單類別(繼承自 Order)
class DiscountOrder extends Order {
private $discount; // 折扣百分比
// 建構子:初始化折扣
public function __construct($customerName, $discount) {
parent::__construct($customerName);
$this->discount = $discount;
}
// 覆寫計算總金額的方法,加上折扣
public function getTotal() {
$total = parent::getTotal(); // 取得父類別的總金額
return $total - ($total * $this->discount / 100);
}
// 顯示訂單資訊,包含折扣
public function displayOrder() {
echo "客戶名稱:" . $this->customerName . "<br>";
echo "商品清單:<br>";
foreach ($this->products as $product) {
echo "- " . $product->getName() . ",價格:$" . $product->getPrice() . "<br>";
}
echo "折扣:" . $this->discount . "%<br>";
echo "折扣後總金額:$" . $this->getTotal() . "<br>";
}
}
// === 測試程式 ===
// 創建商品
$product1 = new Product("蘋果", 50);
$product2 = new Product("香蕉", 30);
$product3 = new Product("葡萄", 100);
// 一般訂單
$order1 = new Order("王小強");
$order1->addProduct($product1);
$order1->addProduct($product2);
$order1->addProduct($product3);
echo "<h3>一般訂單:</h3>";
$order1->displayOrder();
// 折扣訂單
$order2 = new DiscountOrder("李小華", 10); // 10% 折扣
$order2->addProduct($product1);
$order2->addProduct($product2);
$order2->addProduct($product3);
echo "<h3>折扣訂單:</h3>";
$order2->displayOrder();
?>
程式碼解釋
- Product 類別(商品)
- 包含「名稱」與「價格」兩個屬性,並提供方法取得名稱與價格。
- Order 類別(訂單)
- 包含「客戶名稱」和「商品清單」。
- 提供
addProduct()
方法將商品加入訂單,getTotal()
方法計算總金額,displayOrder()
方法顯示訂單資訊。
- DiscountOrder 類別(折扣訂單)
- 繼承自
Order
類別,增加「折扣」屬性。 - 覆寫
getTotal()
方法,計算折扣後的總金額。 - 覆寫
displayOrder()
方法,顯示折扣資訊與折扣後的總金額。
- 繼承自
- 測試程式
- 創建三個商品物件:蘋果、香蕉、葡萄。
- 創建一個一般訂單,加入商品並顯示總金額。
- 創建一個折扣訂單,加入商品並顯示折扣後的總金額。
執行結果
一般訂單:
客戶名稱:王小強
商品清單:
- 蘋果,價格:$50
- 香蕉,價格:$30
- 葡萄,價格:$100
總金額:$180
折扣訂單(10% 折扣):
客戶名稱:李小華
商品清單:
- 蘋果,價格:$50
- 香蕉,價格:$30
- 葡萄,價格:$100
折扣:10%
折扣後總金額:$162
One Comment