Site icon Li-Edward

第6節 PHP 進階用法-物件導向基礎 – 5個小時學會 PHP

5個小時學會 PHP

5個小時學會 PHP

這節課將學習 物件導向程式設計(OOP) 的基礎概念,包括 類別物件屬性方法封裝 以及 繼承。物件導向能讓程式碼更具結構性、可重用性和維護性。未來可以進一步學習「多型」與「抽象類別」等進階概念!


1. 什麼是物件導向程式設計 (OOP)

物件導向是一種程式設計方式,將程式設計視為由物件組成的系統,每個物件都具有自己的 屬性(資料) 和 方法(函式)。

OOP 的三大特性

  1. 封裝(Encapsulation):將資料和操作封裝在類別內。
  2. 繼承(Inheritance):子類別繼承父類別的屬性和方法。
  3. 多型(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)

存取控制修飾詞

  1. public:屬性或方法可以在任何地方被存取。
  2. private:僅能在類別內部被存取。
  3. 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. 解釋


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
這樣可以透過 gettersetter 方法來控制存取邏輯,確保屬性不會被賦予不合理的值。

範例:年齡驗證

<?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,並提供適當的 gettersetter 方法,可以有效地封裝物件,讓程式碼更加穩定且容易維護。

原則: 如果某個屬性或方法不需要外部直接存取,預設設為 private
這樣能讓類別更具封裝性,避免外部不必要的依賴與干擾。


綜合練習訂單管理系統

需求:


程式碼範例:

<?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();
?>

程式碼解釋

  1. Product 類別(商品)
    • 包含「名稱」與「價格」兩個屬性,並提供方法取得名稱與價格。
  2. Order 類別(訂單)
    • 包含「客戶名稱」和「商品清單」。
    • 提供 addProduct() 方法將商品加入訂單,getTotal() 方法計算總金額,displayOrder() 方法顯示訂單資訊。
  3. DiscountOrder 類別(折扣訂單)
    • 繼承自 Order 類別,增加「折扣」屬性。
    • 覆寫 getTotal() 方法,計算折扣後的總金額。
    • 覆寫 displayOrder() 方法,顯示折扣資訊與折扣後的總金額。
  4. 測試程式
    • 創建三個商品物件:蘋果、香蕉、葡萄。
    • 創建一個一般訂單,加入商品並顯示總金額。
    • 創建一個折扣訂單,加入商品並顯示折扣後的總金額。

執行結果

一般訂單:

客戶名稱:王小強  
商品清單:
- 蘋果,價格:$50
- 香蕉,價格:$30
- 葡萄,價格:$100
總金額:$180

折扣訂單(10% 折扣):

客戶名稱:李小華  
商品清單:
- 蘋果,價格:$50
- 香蕉,價格:$30
- 葡萄,價格:$100
折扣:10%
折扣後總金額:$162




主頁 » PHP » 第6節 PHP 進階用法-物件導向基礎 – 5個小時學會 PHP
Exit mobile version