Chuyển đến nội dung chính

第 4 課:泛型、套件和模組

Go 泛型(型別參數、限制)。套件組織、可見性規則、初始化函數。 Go 模組、版本控制、依賴管理、工作區模式。

💻 程式設計 — 第 4 課 第 4 課:泛型、套件和模組

Golang:從基礎到高級

第 1 部分:Go 基礎知識

亞洲開發網

1.Go中的泛型(Go 1.18+)

泛型允許編寫與許多不同類型一起使用的函數和類型,同時仍然保持類型安全。在 Go 1.18之前,必須使用 介面{} 或代碼生成。

1.1.通用函數

package main

import (
    "cmp"
    "fmt"
)

// Generic function với type parameter T
func Max[T cmp.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// Multiple type parameters
func Map[T any, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

// Filter
func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

// Reduce
func Reduce[T any, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

func main() {
    // Type inference - Go tự suy luận type
    fmt.Println(Max(3, 5))           // int: 5
    fmt.Println(Max(3.14, 2.71))     // float64: 3.14
    fmt.Println(Max("apple", "banana")) // string: "banana"
    
    // Explicit type
    fmt.Println(Max[int](10, 20))    // 20
    
    // Map: []int → []string
    nums := []int{1, 2, 3, 4, 5}
    strs := Map(nums, func(n int) string {
        return fmt.Sprintf("num_%d", n)
    })
    fmt.Println(strs) // [num_1, num_2, num_3, num_4, num_5]
    
    // Filter
    evens := Filter(nums, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println(evens) // [2, 4]
    
    // Reduce
    sum := Reduce(nums, 0, func(acc, n int) int {
        return acc + n
    })
    fmt.Println(sum) // 15
}

1.2.類型約束

import "golang.org/x/exp/constraints"

// Built-in constraints (Go 1.21+: cmp package)
// any          = interface{} (mọi type)
// comparable   = types hỗ trợ == và !=
// cmp.Ordered  = types hỗ trợ <, >, <=, >=

// Custom constraint
type Number interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~float32 | ~float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

// ~ (tilde) cho phép underlying types
type Celsius float64
type Fahrenheit float64

// Celsius có underlying type float64, nên match ~float64
var temp Celsius = 36.5
fmt.Println(Sum([]Celsius{36.5, 37.0, 36.8})) // OK nhờ ~float64

// Constraint với methods
type Stringer interface {
    String() string
}

func PrintAll[T Stringer](items []T) {
    for _, item := range items {
        fmt.Println(item.String())
    }
}

// Composite constraint (methods + types)
type StringLike interface {
    ~string
    Len() int
}

1.3.通用類型(結構體、介面)

// Generic Stack
type Stack[T any] struct {
    items []T
}

func NewStack[T any]() *Stack[T] {
    return &Stack[T]{items: make([]T, 0)}
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    last := len(s.items) - 1
    item := s.items[last]
    s.items = s.items[:last]
    return item, true
}

func (s *Stack[T]) Peek() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    return s.items[len(s.items)-1], true
}

func (s *Stack[T]) Len() int {
    return len(s.items)
}

// Generic Result type (like Rust)
type Result[T any] struct {
    Value T
    Err   error
}

func Ok[T any](value T) Result[T] {
    return Result[T]{Value: value}
}

func Fail[T any](err error) Result[T] {
    return Result[T]{Err: err}
}

func (r Result[T]) Unwrap() (T, error) {
    return r.Value, r.Err
}

func main() {
    // Stack of ints
    intStack := NewStack[int]()
    intStack.Push(1)
    intStack.Push(2)
    intStack.Push(3)
    
    val, ok := intStack.Pop()
    fmt.Println(val, ok) // 3, true
    
    // Stack of strings
    strStack := NewStack[string]()
    strStack.Push("hello")
    strStack.Push("world")
    
    // Result
    r := Ok(42)
    v, err := r.Unwrap()
    fmt.Println(v, err) // 42, nil
}

1.4.切片和地圖包(Go 1.21+)

import (
    "fmt"
    "maps"
    "slices"
)

func main() {
    // slices package - generic slice operations
    nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
    
    slices.Sort(nums)                          // [1, 1, 2, 3, 4, 5, 6, 9]
    fmt.Println(slices.Contains(nums, 5))      // true
    fmt.Println(slices.Index(nums, 4))          // 4
    fmt.Println(slices.Min(nums))               // 1
    fmt.Println(slices.Max(nums))               // 9
    
    // slices.SortFunc cho custom sorting
    type Person struct {
        Name string
        Age  int
    }
    people := []Person{
        {"Alice", 30},
        {"Bob", 25},
        {"Carol", 28},
    }
    slices.SortFunc(people, func(a, b Person) int {
        return a.Age - b.Age
    })
    
    // Compact (remove consecutive duplicates)
    sorted := []int{1, 1, 2, 2, 3, 3}
    unique := slices.Compact(sorted) // [1, 2, 3]
    
    // maps package
    m := map[string]int{"a": 1, "b": 2, "c": 3}
    
    keys := slices.Sorted(maps.Keys(m))      // ["a", "b", "c"]
    values := slices.Sorted(maps.Values(m))  // [1, 2, 3]
    
    // Clone
    m2 := maps.Clone(m)
    
    // Equal
    fmt.Println(maps.Equal(m, m2))  // true
    
    fmt.Println(unique, keys, values)
}

2. 套餐

2.1.封裝基礎知識

// Mỗi file Go thuộc một package
// Package = thư mục chứa các file .go cùng package name
// main package = entry point (có func main)

// Quy tắc đặt tên package:
// - Lowercase, single word: user, auth, config
// - Không dùng camelCase hay snake_case
// - Ngắn gọn, mô tả chức năng

// Visibility rules:
// Exported (public)   = Viết hoa chữ cái đầu: User, GetName(), MaxRetries
// Unexported (private) = Viết thường: user, getName(), maxRetries

2.2.包裝組織

// Project structure:
// myapp/
// ├── cmd/api/main.go          ← package main
// ├── internal/
// │   ├── user/
// │   │   ├── handler.go       ← package user
// │   │   ├── service.go       ← package user
// │   │   ├── repository.go    ← package user
// │   │   └── model.go         ← package user
// │   └── auth/
// │       ├── jwt.go           ← package auth
// │       └── middleware.go    ← package auth
// └── pkg/
//     └── validator/
//         └── validator.go     ← package validator

// internal/ → chỉ import được từ parent module
// pkg/      → public, ai cũng import được
// internal/user/model.go
package user

import "time"

// User - Exported (public)
type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    password  string    // unexported (private) - chỉ truy cập trong package user
    CreatedAt time.Time `json:"created_at"`
}

// NewUser - Exported constructor
func NewUser(name, email, password string) *User {
    return &User{
        Name:      name,
        Email:     email,
        password:  hashPassword(password), // gọi private function
        CreatedAt: time.Now(),
    }
}

// hashPassword - unexported (private)
func hashPassword(pwd string) string {
    // ... hash logic
    return pwd
}

2.3. init() 函數

package config

import (
    "fmt"
    "os"
)

var (
    DatabaseURL string
    Port        string
)

// init() chạy TỰ ĐỘNG khi package được import
// Chạy trước main(), theo thứ tự import
func init() {
    DatabaseURL = os.Getenv("DATABASE_URL")
    if DatabaseURL == "" {
        DatabaseURL = "postgres://localhost:5432/mydb"
    }
    
    Port = os.Getenv("PORT")
    if Port == "" {
        Port = "8080"
    }
    
    fmt.Println("Config initialized")
}

// ⚠️ Best practice: Hạn chế dùng init()
// - Khó test, khó debug
// - Implicit side effects
// - Thay bằng explicit initialization: config.Load()

2.4.導入模式

import (
    // Standard library
    "fmt"
    "net/http"
    "os"
    
    // Third-party (blank line separator)
    "github.com/gin-gonic/gin"
    "github.com/jackc/pgx/v5"
    
    // Internal packages (blank line separator)
    "github.com/yourname/myapp/internal/user"
    "github.com/yourname/myapp/internal/auth"
)

// Alias import
import (
    stdhttp "net/http"         // alias
    _ "github.com/lib/pq"     // blank import (chỉ chạy init())
    . "fmt"                    // dot import (KHÔNG khuyến khích)
)

// Blank import (_ ) dùng cho:
// - Database drivers: _ "github.com/lib/pq"
// - Image decoders: _ "image/png"
// Chỉ chạy init(), không dùng package trực tiếp

3.Go模組

3.1.模組基礎知識

# Khởi tạo module
go mod init github.com/yourname/myapp

# go.mod file
module github.com/yourname/myapp

go 1.23

require (
    github.com/gin-gonic/gin v1.10.0
    github.com/jackc/pgx/v5 v5.7.1
    go.uber.org/zap v1.27.0
)

require (
    // indirect dependencies (tự động thêm)
    github.com/bytedance/sonic v1.12.4 // indirect
    github.com/go-playground/validator/v10 v10.23.0 // indirect
    // ...
)

3.2.依賴管理

# Thêm dependency
go get github.com/gin-gonic/gin@latest
go get github.com/gin-gonic/[email protected]   # specific version
go get github.com/gin-gonic/gin@main      # branch

# Update dependency
go get -u github.com/gin-gonic/gin        # latest minor/patch
go get -u=patch github.com/gin-gonic/gin  # latest patch only

# Update tất cả
go get -u ./...

# Remove unused dependencies
go mod tidy

# Verify dependencies (checksum)
go mod verify

# Download dependencies (cho CI/Docker)
go mod download

# Vendor mode (copy deps vào project)
go mod vendor
go build -mod=vendor ./...

# Xem dependency graph
go mod graph

3.3.語意版本控制

// Go sử dụng Semantic Versioning: vMAJOR.MINOR.PATCH
// v1.2.3
// MAJOR: Breaking changes (phải thay đổi import path cho v2+)
// MINOR: New features, backwards compatible
// PATCH: Bug fixes

// Major version v2+ phải thay đổi module path:
// v1: github.com/jackc/pgx
// v5: github.com/jackc/pgx/v5

import "github.com/jackc/pgx/v5"  // Module path bao gồm /v5

// go.mod
require github.com/jackc/pgx/v5 v5.7.1

3.4.工作區模式(Go 1.18+)

# Workspace cho multi-module development
# Khi bạn có nhiều modules liên quan và muốn develop cùng lúc

# Structure:
# workspace/
# ├── go.work
# ├── service-a/
# │   ├── go.mod
# │   └── main.go
# ├── service-b/
# │   ├── go.mod
# │   └── main.go
# └── shared-lib/
#     ├── go.mod
#     └── utils.go

# Khởi tạo workspace
go work init ./service-a ./service-b ./shared-lib

# go.work file
go 1.23

use (
    ./service-a
    ./service-b
    ./shared-lib
)

# Thêm module vào workspace
go work use ./new-service

# ⚠️ KHÔNG commit go.work vào git cho libraries
# CÓ THỂ commit cho monorepo applications

3.5.替換指令

// go.mod - replace cho local development hoặc fork

module github.com/yourname/myapp

go 1.23

require (
    github.com/original/library v1.0.0
)

// Replace với local path (development)
replace github.com/original/library => ../my-fork

// Replace với fork
replace github.com/original/library => github.com/yourfork/library v1.0.1

// ⚠️ Dùng workspace mode thay vì replace khi có thể

4. 建置標籤和條件編譯

// Build tags cho phép include/exclude files khi build

//go:build linux
// +build linux
// File này chỉ compile trên Linux

//go:build !windows
// File này compile trên mọi OS trừ Windows

//go:build integration
// File này chỉ compile khi: go test -tags=integration

// Ví dụ: database driver khác nhau cho từng môi trường
//go:build prod

package config

var DatabaseURL = "postgres://prod-server:5432/mydb"
# Build với tags
go build -tags=prod ./...
go test -tags=integration ./...

5. 總結

  • 泛型:類型參數、限制條件(任意、可比較、cmp.Ordered)、通用結構
  • 切片/貼圖包:集合的通用輔助函數(Go 1.21+)
  • 套餐:大寫 = 導出,小寫 = 未導出,小且集中
  • Go 模組:go.mod、語意版本控制、go mod tidy、供應商模式
  • 工作空間:多模組開發,go.work

下一部分: Goroutine 和通道 — Go 的並發能力,使其成為高效能係統的首選。