本章為通用理論。各語言的具體實踐請參考下方「在各語言中的實踐」。
程式設計範式(Programming Paradigm)是面對問題時的基本切入方式。理解每種範式的核心思想,才知道什麼時候選哪一種,寫出來的程式才會自然。
範式概覽#
| 範式 | 核心思想 | 典型語言 |
|---|---|---|
| 物件導向(Object-Oriented Programming, OOP) | 封裝、繼承、多型 | Java, C++, Python |
| 函式式(Functional Programming, FP) | 不可變性、純函式、高階函式 | Haskell, Scala, Clojure |
| 泛型(Generic Programming) | 型別參數化、程式碼重用 | C++, Java, Go |
| 過程式(Procedural Programming) | 順序執行、狀態修改 | C, Pascal |
現代語言往往支援多種範式並存。重點不是「我寫的是 OOP 還是 FP」,而是「這段需求最適合哪種抽象」。
物件導向#
以「物件」作為基本單元,把資料和操作綁在一起。
三大核心特性#
1. 封裝(Encapsulation)
- 資料和行為綁在一起
- 隱藏內部細節,只暴露介面
- 降低耦合度
2. 繼承(Inheritance)
- 子類繼承父類的屬性與方法
- 達成程式碼重用
- 建立型別層次結構
3. 多型(Polymorphism)
- 同一個介面,多種實作
- 執行時動態綁定
- 提高彈性與可擴充性
對介面設計,而非對實作#
Program to an interface, not an implementation.
這是 OOP 的黃金法則:依賴抽象(介面),而不是依賴具體型別。
Go 介面範例
type Country struct {
Name string
}
type City struct {
Name string
}
type Stringable interface {
ToString() string
}
func (c Country) ToString() string {
return "Country = " + c.Name
}
func (c City) ToString() string {
return "City = " + c.Name
}
func PrintStr(p Stringable) {
fmt.Println(p.ToString())
}
// 使用
d1 := Country{"USA"}
d2 := City{"Los Angeles"}
PrintStr(d1)
PrintStr(d2)Country、City 是領域型別,PrintStr 是控制邏輯,兩者透過 Stringable 解耦。任何實作 Stringable 的型別都能餵給 PrintStr。
函式式#
強調以純函式(Pure Function)與不可變資料為基礎構建程式。
核心概念#
1. 純函式(Pure Function)
- 相同輸入永遠得到相同輸出
- 沒有副作用(不修改外部狀態)
- 易於測試與推理
2. 不可變性(Immutability)
- 資料一旦建立就不能修改
- 要改就建立新的副本
- 避免共享狀態引起的 Bug
3. 高階函式(Higher-Order Function)
- 函式可以當參數傳遞
- 函式可以當回傳值
- 達成更高層次的抽象
Functional Options 模式#
函式式抽象在工程上的代表案例,在 Go 社群非常流行。
Functional Options 模式詳解
問題場景: 建立物件時要設定多個可選參數。
傳統方案的問題:
// 方案一:多個建構函式
func NewDefaultServer(addr string, port int) (*Server, error)
func NewTLSServer(addr string, port int, tls *tls.Config) (*Server, error)
func NewServerWithTimeout(addr string, port int, timeout time.Duration) (*Server, error)
// 組合爆炸...
// 方案二:Config 物件
func NewServer(addr string, port int, conf *Config) (*Server, error)
// 呼叫端要判斷 nil 或空 Config{}Functional Options 解法:
// 定義 Option 型別
type Option func(*Server)
// 各種設定函式
func Protocol(p string) Option {
return func(s *Server) {
s.Protocol = p
}
}
func Timeout(timeout time.Duration) Option {
return func(s *Server) {
s.Timeout = timeout
}
}
func MaxConns(maxconns int) Option {
return func(s *Server) {
s.MaxConns = maxconns
}
}
// 建構函式
func NewServer(addr string, port int, options ...func(*Server)) (*Server, error) {
srv := Server{
Addr: addr,
Port: port,
Protocol: "tcp",
Timeout: 30 * time.Second,
MaxConns: 1000,
}
for _, option := range options {
option(&srv)
}
return &srv, nil
}
// 使用
s1, _ := NewServer("localhost", 1024)
s2, _ := NewServer("localhost", 2048, Protocol("udp"))
s3, _ := NewServer("0.0.0.0", 8080, Timeout(300*time.Second), MaxConns(1000))優點:
- 呼叫端直覺、可讀性高
- 想加新設定不需要動既有 API
- 沒有 nil / 空結構的判斷負擔
- 自帶說明(函式名即是設定意圖)
泛型#
透過型別參數化,把演算法與資料結構獨立於具體型別。
核心價值#
- 程式碼重用:同一套邏輯處理不同型別
- 型別安全:編譯期就抓到型別錯誤
- 效能:避免執行時的型別檢查與轉型
常見應用:集合(List、Map、Set)、演算法(排序、搜尋)、工具型別(Optional、Future)。
控制反轉與委託#
控制反轉(Inversion of Control, IoC)的核心是:把控制邏輯從業務邏輯中抽離出來。
不要在業務邏輯裡寫控制邏輯。讓業務邏輯依賴控制邏輯,而不是反過來。
比喻: 開關與電燈
- 開關是控制邏輯
- 電燈是業務邏輯
- 不要在電燈裡實作開關。把開關抽成介面(協定),讓電燈依賴它
控制反轉範例:Undo 功能
傳統做法(控制邏輯依賴業務邏輯):
type UndoableIntSet struct {
IntSet // 嵌入業務邏輯
functions []func()
}
func (set *UndoableIntSet) Add(x int) {
// Undo 邏輯與 IntSet 業務邏輯緊密耦合
if !set.Contains(x) {
set.data[x] = true
set.functions = append(set.functions, func() { set.Delete(x) })
}
}控制反轉做法(業務邏輯依賴控制邏輯):
// 定義控制邏輯(協定)
type Undo []func()
func (undo *Undo) Add(function func()) {
*undo = append(*undo, function)
}
func (undo *Undo) Undo() error {
// ... 純粹的 Undo 邏輯
}
// 業務邏輯依賴控制邏輯
type IntSet struct {
data map[int]bool
undo Undo // 嵌入控制邏輯
}
func (set *IntSet) Add(x int) {
if !set.Contains(x) {
set.data[x] = true
set.undo.Add(func() { set.Delete(x) })
}
}好處: Undo 不再綁定特定業務型別,任何資料結構都能套用。
各範式的適用場景#
| 場景 | 推薦範式 | 原因 |
|---|---|---|
| 建模複雜業務領域 | 物件導向 | 類別自然對應現實世界的概念 |
| 資料轉換與處理 | 函式式 | 純函式與不可變性讓資料流清晰 |
| 通用資料結構與演算法 | 泛型 | 型別參數化達成高度重用 |
| 簡單腳本與工具 | 過程式 | 直接、最少抽象 |
| 並行程式設計 | 函式式 | 不可變性避開競爭條件 |
實務上幾乎都是混合使用:類別建模業務、Stream 處理集合、泛型寫容器。語言層面早就不再壁壘分明。
在各語言中的實踐#
- Go 語言:以過程式為基底,透過介面與結構體嵌入達成「組合優於繼承」的輕量物件導向;以通道(Channel)與共常式(Goroutine)體現 CSP(Communicating Sequential Processes)併發模型。
- Kotlin Coroutines:JVM 上的 OOP + FP 融合,並用 suspend function 與結構化併發解掉回呼地獄。
- 語言實作:從 VM、bytecode、GC 到 JIT,看這些範式在語言層底下是怎麼被實現的。