Series Go Backend: Structs - Khai báo và sử dụng kiểu dữ liệu tùy chỉnh
Chào mừng bạn đến với Phần 2 của series: Cấu trúc dữ liệu và Hướng đối tượng. Nếu bạn bước sang Go từ các ngôn ngữ thuần OOP như C++, Java hay PHP, có lẽ bạn sẽ cố gắng tìm kiếm từ khóa class. Sự thật là: Go không có class.
Để đại diện cho các thực thể trong thế giới thực (như User, Order, Product), Go sử dụng Struct (Structure) - một tập hợp các trường (fields) dữ liệu được gom nhóm lại với nhau.
1. Khai báo Struct cơ bản
Bạn định nghĩa một struct bằng cách sử dụng từ khóa type kết hợp với struct.
Go
// Định nghĩa thực thể User
type User struct {
ID int64
Username string
Email string
isActive bool // Viết thường: Thuộc tính Private, chỉ dùng nội bộ trong package
}
Quy tắc Exported (Public) và Unexported (Private) bằng chữ cái đầu tiên không chỉ áp dụng cho tên hàm, tên biến mà áp dụng cho cả tên Struct và các trường bên trong Struct. Nếu bạn viết isActive chữ i thường, các package khác (như package JSON serializer) sẽ không thể nhìn thấy trường này.
2. Các cách khởi tạo Struct
Trong Go, bạn có nhiều cú pháp để tạo ra một "object" từ định nghĩa Struct tùy vào mục đích sử dụng.
Cách 1: Khởi tạo ngầm định (Zero Value) Tất cả các trường bên trong Struct sẽ tự động nhận giá trị Zero Value của kiểu dữ liệu tương ứng.
Go
var u1 User
u1.ID = 1024
u1.Username = "hieudev"
// u1.Email hiện tại là chuỗi rỗng ""
// u1.isActive hiện tại là false
Cách 2: Khởi tạo với tên trường (Named fields) - Khuyên dùng Đây là cách phổ biến nhất vì nó cực kỳ rõ ràng, dễ đọc và không bắt buộc bạn phải nhớ thứ tự khai báo của các trường.
Go
u2 := User{
ID: 2048,
Username: "admin",
Email: "admin@example.com",
// Các trường không được liệt kê (như isActive) sẽ nhận Zero Value
}
Cách 3: Khởi tạo bằng Con trỏ (Pointer to Struct) Trong Backend, khi trả về một Struct từ database, chúng ta thường trả về con trỏ của Struct đó để tránh chi phí copy bộ nhớ (như đã phân tích ở bài Pointers).
Go
// u3 lúc này có kiểu dữ liệu là *User
u3 := &User{
ID: 4096,
Username: "moderator",
}
// Go rất thông minh, bạn không cần dùng (*u3).Username để truy cập biến con trỏ
// Bạn có thể truy cập trực tiếp bằng dấu chấm (.)
fmt.Println(u3.Username)
3. Anonymous Struct (Struct ẩn danh)
Đôi khi, bạn chỉ cần gom nhóm một vài dữ liệu để parse JSON payload từ request gửi lên, hoặc để cấu hình nhanh một thứ gì đó mà không muốn phải định nghĩa hẳn một kiểu type riêng biệt. Struct ẩn danh sinh ra để làm việc này: "Dùng một lần rồi bỏ".
Go
func main() {
// Khai báo cấu trúc và khởi tạo dữ liệu ngay lập tức
dbConfig := struct {
Host string
Port int
}{
Host: "127.0.0.1",
Port: 3306,
}
fmt.Printf("Connecting to %s:%d\n", dbConfig.Host, dbConfig.Port)
}
4. Struct Tags: "Vũ khí tối thượng" của Backend Go
Một tính năng cực kỳ độc đáo và mạnh mẽ của Struct trong Go là Tags. Bằng cách thêm một chuỗi được bọc trong cặp dấu backtick (`) ở phía sau mỗi trường, bạn có thể đính kèm metadata vào trường đó.
Các thư viện bên ngoài sẽ dùng kỹ thuật Reflection để đọc các metadata này lúc Runtime nhằm xử lý dữ liệu tự động.
Go
type Product struct {
// Tag "json" giúp thư viện encoding/json biết cách map tên trường khi convert sang JSON
// Tag "gorm" giúp thư viện GORM biết đây là khóa chính của bảng trong Database
ID int64 `json:"id" gorm:"primaryKey"`
// Tag "validate" giúp package go-playground/validator tự động kiểm tra logic
Name string `json:"name" validate:"required,min=3"`
// Thuộc tính omitempty: Nếu Price = 0, loại bỏ hoàn toàn trường này khỏi JSON trả về
Price int `json:"price,omitempty"`
// Bỏ qua hoàn toàn trường này khi trả về JSON (rất hữu ích để giấu mật khẩu)
Stock int `json:"-"`
}
Nếu không có Struct Tags, bạn sẽ phải viết hàng tá dòng code thủ công để parse tên trường, ánh xạ cột database hay validate từng biến một. Đây là một trong những tính năng bạn sẽ sử dụng hàng ngày khi xây dựng RESTful API bằng Go.
All rights reserved