Cơ bản về reflection
Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 83 trên 107.
Phản chiếu (reflection) cho phép chương trình kiểm tra và thao tác với cấu trúc của chính nó khi chạy. Gói reflect của Go cung cấp khả năng này, giúp bạn kiểm tra kiểu dữ liệu, đọc các trường của struct và gọi phương thức một cách linh hoạt.
Hai kiểu cốt lõi trong reflection là reflect.Type và reflect.Value. Bạn lấy chúng bằng cách dùng reflect.TypeOf() và reflect.ValueOf():
import "reflect"
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
v := reflect.ValueOf(p)
fmt.Println(t.Name()) // Person
fmt.Println(t.Kind()) // struct
fmt.Println(v.NumField()) // 2Bạn có thể lặp qua các trường của struct để xem tên, kiểu dữ liệu và giá trị của chúng:
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("%s: %v\n", field.Name, value)
}
// Đầu ra:
// Name: Alice
// Age: 30Reflection rất mạnh mẽ nhưng cũng có những đánh đổi: bỏ qua việc kiểm tra kiểu dữ liệu khi biên dịch, chạy chậm hơn so với truy cập trực tiếp và khiến code khó hiểu hơn. Chỉ dùng reflection khi cần cho các tác vụ như chuyển đổi dữ liệu JSON, ánh xạ ORM hoặc xây dựng các tiện ích tổng quát khi chưa biết kiểu dữ liệu lúc biên dịch.
Thử thách
DễHãy xây dựng một công cụ kiểm tra struct, sử dụng reflection để xem xét mọi struct và báo cáo cấu trúc của nó! Đây là một trường hợp sử dụng thực tế của reflection: tạo các tiện ích có thể hoạt động với những kiểu dữ liệu chưa biết khi biên dịch.
Bạn sẽ tổ chức code trong hai tệp:
inspector.go: Tạo tiện ích kiểm tra dựa trên reflection.Xây dựng một hàm
InspectStruct(v any) stringsử dụng góireflectđể xem xét mọi struct được truyền vào. Hàm cần trả về một báo cáo được định dạng, bao gồm:- Tên kiểu của struct
- Loại của struct (phải là "struct")
- Số lượng trường
- Với mỗi trường: tên trường, kiểu và giá trị hiện tại
Định dạng đầu ra như sau:
Type: [TypeName] Kind: struct Fields: [count] - [FieldName] ([FieldType]): [Value] - [FieldName] ([FieldType]): [Value] ...Dùng
reflect.TypeOf()để lấy thông tin kiểu vàreflect.ValueOf()để truy cập giá trị các trường. Duyệt qua các trường bằngNumField(), dùngField(i)trên kiểu để lấy siêu dữ liệu và dùngField(i)trên giá trị để lấy các giá trị thực tế.main.go: Định nghĩa các struct mẫu và dùng công cụ kiểm tra.Đọc kiểu struct (
producthoặcemployee) cùng các giá trị trường, sau đó tạo struct tương ứng và kiểm tra nó.Với
product: Tạo structProductcó các trườngName(string),Price(float64) vàInStock(bool). Đọc tên, giá và trạng thái còn hàng.Với
employee: Tạo structEmployeecó các trườngID(int),Name(string) vàDepartment(string). Đọc ID, tên và phòng ban.Truyền struct đã tạo vào
InspectStructvà in kết quả.
Đầu vào sẽ có dạng sau:
- Dòng 1: Kiểu struct (
producthoặcemployee) - Các dòng tiếp theo: Giá trị các trường của kiểu struct đó
Ví dụ, với đầu vào:
product
Laptop
999.99
trueĐầu ra cần là:
Type: Product
Kind: struct
Fields: 3
- Name (string): Laptop
- Price (float64): 999.99
- InStock (bool): trueVà với đầu vào:
employee
42
Alice Johnson
EngineeringĐầu ra cần là:
Type: Employee
Kind: struct
Fields: 3
- ID (int): 42
- Name (string): Alice Johnson
- Department (string): EngineeringVà với đầu vào:
product
Headphones
79.50
falseĐầu ra cần là:
Type: Product
Kind: struct
Fields: 3
- Name (string): Headphones
- Price (float64): 79.5
- InStock (bool): falseHãy chú ý cách công cụ kiểm tra hoạt động với nhiều kiểu struct khác nhau mà không cần biết trước cấu trúc của chúng. Đó là sức mạnh của reflection! Cùng một hàm InspectStruct có thể xử lý linh hoạt cả struct Product lẫn Employee.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// Struct Product có các trường Name, Price và InStock
type Product struct {
Name string
Price float64
InStock bool
}
// Struct Employee có các trường ID, Name và Department
type Employee struct {
ID int
Name string
Department string
}
func main() {
reader := bufio.NewReader(os.Stdin)
// Đọc kiểu struct
structType, _ := reader.ReadString('\n')
structType = strings.TrimSpace(structType)
if structType == "product" {
// Đọc các trường của sản phẩm
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
priceStr, _ := reader.ReadString('\n')
priceStr = strings.TrimSpace(priceStr)
price, _ := strconv.ParseFloat(priceStr, 64)
inStockStr, _ := reader.ReadString('\n')
inStockStr = strings.TrimSpace(inStockStr)
inStock, _ := strconv.ParseBool(inStockStr)
// TODO: Tạo struct Product bằng các giá trị đã đọc
// TODO: Gọi InspectStruct với sản phẩm và in ra kết quả
} else if structType == "employee" {
// Đọc các trường của nhân viên
idStr, _ := reader.ReadString('\n')
idStr = strings.TrimSpace(idStr)
id, _ := strconv.Atoi(idStr)
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
department, _ := reader.ReadString('\n')
department = strings.TrimSpace(department)
// TODO: Tạo struct Employee bằng các giá trị đã đọc
// TODO: Gọi InspectStruct với nhân viên và in ra kết quả
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker pool12Các khái niệm OOP nâng cao
Mẫu tùy chọn hàmMẫu Builder trong GoChuỗi phương thứcBí danh kiểu dữ liệu và định nghĩa kiểuCơ bản về reflectionTổng quan về tạo codeTự luyện tập: Trình biên dịch Go online