當我們開始設計和撰寫程式碼時,我們經常遇到的挑戰之一是如何創建易於維護和擴展的程式碼。為了解決這些問題,軟體工程師們引入了SOLID原則,這是一組五個基本原則,用於指導物件導向設計。在這篇部落格中,我們將深入探討每個原則,提供錯誤和正確的程式碼示例,以及相關的解釋。

單一職責原則 (Single Responsibility Principle, SRP)

原則: 一個類別應該只有一個單一的職責。

錯誤範例:

1
2
3
4
5
6
7
8
9
class User {
public function createUser($userData) {
// 創建新使用者
}

public function sendEmail($userEmail, $message) {
// 發送郵件
}
}

在上面的錯誤範例中,User 類別負責創建使用者和發送郵件,這違反了單一職責原則。

正確範例:

1
2
3
4
5
6
7
8
9
10
11
class User {
public function createUser($userData) {
// 創建新使用者
}
}

class EmailService {
public function sendEmail($userEmail, $message) {
// 發送郵件
}
}

在正確的範例中,我們將創建使用者和發送郵件的職責分開為兩個不同的類別,每個類別只負責一個單一的職責。

開放封閉原則 (Open/Closed Principle, OCP)

原則: 類別應該是開放擴展的,但封閉修改的。

錯誤範例:

1
2
3
4
5
class Circle {
public function calculateArea($radius) {
return 3.14 * $radius * $radius;
}
}

如果我們需要新增一個矩形的計算面積功能,我們必須修改 Circle 類別,這違反了開放封閉原則。

正確範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
interface Shape {
public function calculateArea();
}

class Circle implements Shape {
private $radius;

public function __construct($radius) {
$this->radius = $radius;
}

public function calculateArea() {
return 3.14 * $this->radius * $this->radius;
}
}

class Rectangle implements Shape {
private $width;
private $height;

public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}

public function calculateArea() {
return $this->width * $this->height;
}
}

在正確的範例中,我們使用介面 Shape 定義了計算面積的方法,並實現了不同的形狀,以擴展功能,而不需要修改原始的 Circle 類別。

里氏替換原則 (Liskov Substitution Principle, LSP)

原則: 子類別應該能夠替代父類別而不引起錯誤。

錯誤範例:

1
2
3
4
5
6
7
8
9
10
11
class Bird {
public function fly() {
// 鳥飛行的實作
}
}

class Ostrich extends Bird {
public function fly() {
throw new Exception("駝鳥不能飛行");
}
}

在上面的錯誤範例中,Ostrich 繼承自 Bird 並重寫了 fly 方法,但它的實作是拋出一個異常,這違反了里氏替換原則。

正確範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Flyable {
public function fly();
}

class Bird implements Flyable {
public function fly() {
// 鳥飛行的實作
}
}

class Ostrich implements Flyable {
public function fly() {
throw new Exception("駝鳥不能飛行");
}
}

在正確的範例中,我們使用介面 Flyable 定義了 fly 方法,並確保所有實現了 Flyable 的類別都能夠替代彼此。

介面隔離原則 (Interface Segregation Principle, ISP)

原則: 一個介面應該只包含客戶端所需的方法。

錯誤範例:

1
2
3
4
interface Worker {
public function work();
public function eat();
}

在上面的錯誤範例中,Worker 介面包含了 work 和 eat 兩個方法,但某些工作者可能不需要 eat 方法,這違反了介面隔離原則。

正確範例:

1
2
3
4
5
6
7
interface Workable {
public function work();
}

interface Eatable {
public function eat();
}

在正確的範例中,我們將 Worker 介面分為兩個單獨的介面,以確保每個介面只包含客戶端所需的方法。

依賴反轉原則 (Dependency Inversion Principle, DIP)

原則: 高層次模組不應該依賴於低層次模組,兩者都應該依賴於抽象。

錯誤範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class LightBulb {
public function turnOn() {
// 開燈的實作
}
}

class Switch {
private $bulb;

public function __construct() {
$this->bulb = new LightBulb();
}

public function operate() {
$this->bulb->turnOn();
}
}

在上面的錯誤範例中,Switch 類別直接依賴於 LightBulb,違反了依賴反轉原則。

正確範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Switchable {
public function turnOn();
}

class LightBulb implements Switchable {
public function turnOn() {
// 開燈的實作
}
}

class Switch {
private $device;

public function connect(Switchable $device) {
$this->device = $device;
}

public function operate() {
$this->device->turnOn();
}
}

在正確的範例中,我們使用介面 Switchable 定義了 turnOn 方法,並讓 Switch 依賴於抽象介面而不是具體的 LightBulb。這符合依賴反轉原則。

總結來說,SOLID原則為我們提供了設計高品質、可維護和擴展的程式碼的指導原則。遵守這些原則可以幫助我們減少錯誤的風險,提高程式碼的品質,並使我們的應用程式更容易適應未來的變化。這是每個軟體工程師都應該了解和應用的重要原則之一。