Like Share Discussion Bookmark Smile

J.J. Huang   2025-03-02   Getting Started Golang 04.函式   瀏覽次數:次   DMCA.com Protection Status

Go | 了解函式作為變數應用

💬 簡介

在 Go 語言中,函式不僅是程式中的基本執行單位,還可以作為變數來使用,這使得函式在應用上更加靈活。
我們可以將函式指派給變數,作為參數傳遞給其他函式,甚至可以讓函式回傳另一個函式。這種特性提供了許多高階應用,例如回呼函式(Callback)、函式組合(Function Composition)與動態行為調整

本篇文章將深入探討函式作為變數的應用,並提供範例來展示如何利用這一特性提升程式的彈性與可讀性。

圖片來源:Gophers


🔎 函式作為變數的應用

在 Go 語言中,函式可以被指派給變數,這使得我們能夠動態決定要執行哪個函式。這類似於其他語言中的「一級函式(First-Class Function)」概念。

1️⃣ 將函式指派給變數

在 Go 中,函式可以被儲存在變數中,並透過該變數來調用:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func sayHello() {
fmt.Println("Hello, Go!")
}

func main() {
var greet func() // 定義一個函式型別的變數
greet = sayHello // 將函式指派給變數
greet() // 呼叫函式
}

輸出結果:

1
Hello, Go!

📝 在這個範例中,我們先宣告了一個 func() 型別的變數 greet,接著將 sayHello 指派給它,最後透過 greet() 來執行函式。

2️⃣ 直接宣告匿名函式

我們也可以直接使用 匿名函式(Anonymous Function) 來建立函式變數,而不需要額外定義一個函式名稱。

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
greet := func(name string) {
fmt.Println("Hello,", name)
}

greet("Alice") // 呼叫匿名函式
}

輸出結果:

1
Hello, Alice

📝 這種方式常用於簡單的回呼函式(Callback)或作為參數傳遞的函式。

3️⃣ 函式作為參數傳遞

函式可以被當作參數傳遞給其他函式,使我們可以在函式內部調用不同的邏輯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

// 定義一個函式,接受另一個函式作為參數
func execute(fn func(string), name string) {
fn(name)
}

func main() {
sayHello := func(name string) {
fmt.Println("Hello,", name)
}

execute(sayHello, "Bob") // 傳遞函式作為參數
}

輸出結果:

1
Hello, Bob

📝 execute 函式接收一個 func(string) 型別的函式作為參數,這讓我們能夠傳遞不同的函式來執行。

4️⃣ 函式作為回傳值

除了作為參數傳遞,我們也可以讓函式回傳另一個函式,進一步提升靈活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

// 回傳一個函式
func getGreeting() func(string) string {
return func(name string) string {
return "Hello, " + name
}
}

func main() {
greet := getGreeting() // 獲取回傳的函式
fmt.Println(greet("Charlie"))
}

輸出結果:

1
Hello, Charlie

📝 getGreeting 回傳一個匿名函式,該函式可用於產生問候語。

5️⃣ 結合函式變數與高階函式

透過函式變數,我們可以實現函式組合(Function Composition)或動態行為調整。例如,我們可以建立一個計算函式,並傳入不同的運算邏輯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

// 計算函式,接受兩個數字與一個運算函式
func compute(a, b int, op func(int, int) int) int {
return op(a, b)
}

func main() {
add := func(x, y int) int { return x + y }
multiply := func(x, y int) int { return x * y }

fmt.Println("加法結果:", compute(10, 5, add)) // 15
fmt.Println("乘法結果:", compute(10, 5, multiply)) // 50
}

輸出結果:

1
2
加法結果: 15
乘法結果: 50

📝 這樣的設計可以讓計算函式更加靈活,根據需求動態決定使用哪種運算方式。

6️⃣ 責任鏈模式應用

責任鏈模式是一種行為設計模式,它允許將請求沿著處理者鏈傳遞,每個處理者可以選擇處理請求或將其傳遞給下一個處理者。在 Go 中,我們可以使用函式變數來動態組合處理流程。

責任鏈模式範例

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
30
31
32
33
34
35
36
37
38
39
package main

import "fmt"

// Handler 定義處理函式的型別
type Handler func(string) string

// 設定一個中介函式來組合處理流程
func chainHandlers(handlers ...Handler) Handler {
return func(input string) string {
result := input
for _, handler := range handlers {
result = handler(result)
}
return result
}
}

func main() {
// 定義多個處理函式
trim := func(s string) string {
return fmt.Sprintf("Trim[%s]", s)
}

toUpper := func(s string) string {
return fmt.Sprintf("Upper[%s]", s)
}

addPrefix := func(s string) string {
return fmt.Sprintf("Prefix[%s]", s)
}

// 建立責任鏈
process := chainHandlers(trim, toUpper, addPrefix)

// 執行處理流程
result := process("hello golang")
fmt.Println(result)
}

輸出結果:

1
Prefix[Upper[Trim[hello golang]]]

📝 我們使用 chainHandlers 來接收多個 Handler,並按照順序依次執行。這種模式可以靈活組合不同的處理流程,例如用於請求過濾、日誌處理、管道處理等場景。


🚀 應用場景

函式變數的應用場景非常廣泛,以下列舉幾個常見應用。

日誌記錄(Logging)

動態設定不同的日誌級別。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

type Logger func(string)

func main() {
infoLogger := func(msg string) { fmt.Println("[INFO]:", msg) }
errorLogger := func(msg string) { fmt.Println("[ERROR]:", msg) }

log := infoLogger // 預設使用 infoLogger
log("Application started")

log = errorLogger // 切換到 errorLogger
log("An error occurred")
}

輸出結果:

1
2
[INFO]: Application started
[ERROR]: An error occurred

HTTP 請求處理(Middleware)

Go 的 HTTP 伺服器可利用函式變數來動態設定請求處理方式,例如加入身份驗證、日誌、錯誤處理等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"net/http"
)

// Middleware: 日誌函式
func loggerMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Request received:", r.URL.Path)
next(w, r)
}
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, Go Web!")
}

func main() {
http.HandleFunc("/", loggerMiddleware(helloHandler))
fmt.Println("Server started on :8080")
http.ListenAndServe(":8080", nil)
}

當訪問 localhost:8080,終端會輸出:

1
Request received: /

⚠️ 注意事項

1️⃣ 函式型別必須匹配

當將函式指派給變數或作為參數傳遞時,函式的參數與回傳值型別必須完全匹配,否則會導致編譯錯誤。

1
2
3
4
5
6
7
8
package main

func main() {
var fn func(int) // 函式變數的定義
fn = func(a string) { // ⚠️ 錯誤:型別不匹配
println(a)
}
}

這段程式碼會導致錯誤:

1
2
3
# command-line-arguments
.\main.go:4:6: declared and not used: fn
.\main.go:5:7: cannot use func(a string) {…} (value of type func(a string)) as func(int) value in assignment

2️⃣ 避免未初始化的函式變數

如果函式變數未初始化就直接執行,會導致 nil pointer dereference 錯誤。

1
2
3
4
5
6
package main

func main() {
var fn func()
fn() // ⚠️ 錯誤:未初始化的函式變數
}

這段程式碼會導致錯誤:

1
2
3
4
5
6
7
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0x9b9a0c]

goroutine 1 [running]:
main.main()
D:/workspace/example/main.go:5 +0xc
exit status 2

🎯總結

  • Go 支援將函式作為變數來使用,使得函式可以動態指派、作為參數傳遞或作為回傳值。
  • 使用函式變數可以讓程式更加靈活,例如實現回呼函式、高階函式、動態行為調整等應用。
  • 在使用函式變數時,需要確保函式型別匹配,並避免執行未初始化的函式變數。
    透過靈活運用函式變數,我們可以寫出更模組化、彈性強的 Go 程式,提高可維護性與可讀性。

最後建議回顧一下 Go | 菜鳥教學 目錄,了解其章節內容。


註:以上參考了
Go