Mars's Blog

開發原則與程式結構

一、說明

良好的程式結構,有助不但容易閱讀理解,出問題時追蹤問題點也容易。什麼樣才算是良好的結構?其實只要尊守物件導向設計原則,並注意程式的層次性即可。

二、物件導向設計原則(SOLID)

SOLID 是五個物件導向設計原則的縮寫,遵循這些原則可以提高程式的可維護性與擴展性。

  1. [S]單一功能原則 (Single Responsibility Principle, SRP)
  2. [O]開閉原則 (Open-Closed Principle, OCP)
  3. [L]里氏替換原則 (Liskov Substitution Principle, LSP)
  4. [I]介面隔離原則 (Interface Segregation Principle, ISP)
  5. [D]依賴反轉原則 (Dependency Inversion Principle, DIP)

單一功能原則 (Single Responsibility Principle, SRP)

定義:一個類別應該只有一個引起它變化的原因,即一個類別只負責一項職責。

說明:當一個類別承擔太多職責時,修改其中一個職責可能影響其他職責。將職責分離可降低耦合度。

案例

1
2
3
4
5
6
7
8
9
10
11
12
// ❌ 違反 SRP:一個類別處理多個職責
class User {
public function save() { /* 資料庫操作 */ }
public function sendEmail() { /* 發送郵件 */ }
public function generateReport() { /* 產生報表 */ }
}

// ✅ 遵守 SRP:各司其職
class User { /* 使用者資料 */ }
class UserRepository { public function save(User $user) { } }
class EmailService { public function send($to, $subject) { } }
class ReportGenerator { public function generate(User $user) { } }

開閉原則 (Open-Closed Principle, OCP)

定義:軟體實體應該對擴展開放,對修改封閉。

說明:新增功能時應透過擴展而非修改現有程式碼,避免影響既有功能。

案例:使用介面與繼承實現擴展,而非修改原始類別。

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

定義:子類別必須能夠替換其父類別,且不影響程式正確性。

說明:子類別應該增強而非削弱父類別的功能。

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

定義:客戶端不應該依賴它不需要的介面。

說明:應將龐大的介面拆分成更小、更具體的介面,讓客戶端只需知道它需要的方法。

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

定義:高層模組不應該依賴低層模組,兩者都應該依賴抽象。

說明:依賴介面而非具體實現,可提高程式的靈活性與可測試性。

DRY原則

Don’t repeat yourself (DRY)、Once and only once(OAOO)

是物件導向程式設計中的基本原則,程式設計師的行事準則。旨在軟體開發中,減少重複的資訊。

參考:Wiki 一次且僅一次

KISS原則

Keep It Simple, Stupid (KISS)

KISS 原則是指在設計當中應當注重簡約的原則,而不摻入非必要的複雜性,這樣的系統運作成效會取得最優。

參考:Wiki KISS原則

三、程式的層次性

良好的程式應該有清晰的分層架構,每層負責不同的職責,降低耦合度。

常見分層架構

三層架構 (Three-Tier Architecture)

1
2
3
4
5
6
7
┌─────────────────┐
│ Presentation │ ← Controller / View (展示層)
├─────────────────┤
│ Business Logic │ ← Service (業務邏輯層)
├─────────────────┤
│ Data Access │ ← Repository / Model (資料存取層)
└─────────────────┘

各層職責

1. Controller (控制層)

  • 接收使用者請求
  • 驗證輸入參數
  • 呼叫 Service 處理業務邏輯
  • 回傳結果給 View

2. Service (服務層/業務邏輯層)

  • 處理業務邏輯
  • 協調多個 Repository
  • 處理交易 (Transaction)
  • 不應包含資料庫操作細節

3. Repository (資料存取層)

  • 封裝資料庫操作
  • 提供資料的 CRUD 方法
  • 隱藏資料來源細節(可能是資料庫、API、快取等)

4. Model (資料模型層)

  • 定義資料結構
  • 資料驗證規則
  • 業務規則

實例說明

Controller 層

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Controller:處理 HTTP 請求
// app/Http/Controllers/UserController.php
namespace App\Http\Controllers;

use App\Services\UserService;
use Illuminate\Http\Request;

class UserController extends Controller
{
public function __construct(private UserService $userService) {}

public function create(Request $request)
{
// 1. 驗證輸入
$validated = $request->validate([...]);
// 2. 呼叫 Service 處理業務邏輯
$user = $this->userService->createUser($validated);
// 3. 回傳結果
return response()->json($user);
}
}

Service 層

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Service:業務邏輯
// app/Services/UserService.php
namespace App\Services;

use App\Repositories\UserRepository;

class UserService
{
public function __construct(private UserRepository $userRepository) {}

public function createUser(array $data)
{
// 業務邏輯:檢查使用者是否存在
if ($this->userRepository->existsByEmail($data['email'])) {
throw new \Exception('Email already exists');
}

// 建立使用者
return $this->userRepository->create($data);
}
}

Repository 層

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Repository:資料存取
// app/Repositories/UserRepository.php
namespace App\Repositories;

use App\Models\User;

class UserRepository
{
public function create(array $data)
{
return User::create($data);
}

public function existsByEmail(string $email)
{
return User::where('email', $email)->exists();
}
}

Model 層

1
2
3
4
5
6
7
8
9
10
// Model:資料庫存取
// app/Models/User.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
protected $fillable = ['name', 'email', 'password'];
}

分層的優點

  • 關注點分離:每層專注於自己的職責
  • 易於測試:可獨立測試各層
  • 易於維護:修改某層不影響其他層
  • 可重用性:Service 可被多個 Controller 使用

四、相關文章

參考