Struct tổng quát
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 70 trên 107.
Giống như hàm, struct cũng có thể có tham số kiểu. Struct tổng quát cho phép bạn tạo cấu trúc dữ liệu hoạt động với mọi kiểu dữ liệu mà vẫn đảm bảo an toàn kiểu. Điều này rất phù hợp để xây dựng các vùng chứa có thể tái sử dụng như ngăn xếp, hàng đợi hoặc cặp khóa-giá trị.
Để định nghĩa một cấu trúc tổng quát, hãy đặt tham số kiểu sau tên cấu trúc:
type Box[T any] struct {
Value T
}
func main() {
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}
fmt.Println(intBox.Value) // 42
fmt.Println(strBox.Value) // hello
}Không giống như các hàm thông thường, bạn phải chỉ định rõ kiểu khi tạo một instance của struct tổng quát. Go không thể suy luận kiểu chỉ dựa vào ngữ cảnh.
Các struct tổng quát có thể có nhiều tham số kiểu, nên rất phù hợp với những cấu trúc như cặp hoặc bản đồ:
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}
fmt.Printf("%s: %d\n", p.Key, p.Value) // age: 25Bạn cũng có thể áp dụng các ràng buộc để bảo đảm kiểu dữ liệu được lưu trữ hỗ trợ những phép toán cụ thể:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}Các cấu trúc generic là nền tảng để xây dựng cấu trúc dữ liệu có kiểu an toàn và có thể tái sử dụng trong Go. Trong bài học tiếp theo, bạn sẽ học cách định nghĩa phương thức cho các kiểu generic này.
Thử thách
DễHãy xây dựng một hệ thống quản lý kho tổng quát có thể theo dõi nhiều loại mặt hàng! Bạn sẽ tạo các cấu trúc vùng chứa có thể tái sử dụng, hoạt động với mọi kiểu dữ liệu mà vẫn đảm bảo an toàn kiểu dữ liệu.
Bạn sẽ sắp xếp code trong hai tệp:
inventory.go: Định nghĩa các cấu trúc vùng chứa tổng quát.Tạo một struct tổng quát
Item[T any]với hai trường:Name(string) vàData(kiểu T). Struct này đại diện cho bất kỳ mặt hàng nào có dữ liệu đi kèm thuộc một kiểu linh hoạt.Tạo một struct tổng quát
Container[T any]với một trường duy nhất làItems, chứa một slice gồm các phần tửItem[T]. Vùng chứa này có thể lưu trữ nhiều mặt hàng cùng kiểu dữ liệu.Tạo một struct tổng quát có ràng buộc
PricedItem[T int | float64]với ba trường:Name(string),Quantity(int) vàPrice(kiểu T). Ràng buộc này đảm bảo giá luôn có kiểu số.Triển khai một hàm
NewContainer[T any]() *Container[T]để tạo và trả về con trỏ đến một Container rỗng.Triển khai phương thức
Addtrên*Container[T]. Phương thức này nhận tên (string) và dữ liệu (T), tạo một Item rồi thêm Item đó vào slice Items của vùng chứa.Triển khai phương thức
CounttrênContainer[T]để trả về số lượng mặt hàng trong vùng chứa.main.go: Minh họa các cấu trúc tổng quát với nhiều kiểu dữ liệu khác nhau.Đọc loại mặt hàng (
string,inthoặcpriced), sau đó đọc số lượng rồi đọc thông tin chi tiết của từng mặt hàng.Với kiểu
string: Đọc từng cặp tên và dữ liệu chuỗi. Tạo mộtContainer[string], thêm tất cả mặt hàng, rồi in từng mặt hàng theo định dạng[Name]: [Data], sau đó in tổng số lượng.Với kiểu
int: Đọc từng cặp tên và dữ liệu số nguyên. Tạo mộtContainer[int], thêm tất cả mặt hàng, rồi in từng mặt hàng theo định dạng[Name]: [Data], sau đó in tổng số lượng.Với kiểu
priced: Đọc từng bộ ba gồm tên, số lượng và giá (dạng số thực). Tạo trực tiếp các instancePricedItem[float64]rồi in từng mặt hàng theo định dạng[Name] x[Quantity] @ [Price], trong đó giá hiển thị một chữ số thập phân.In dòng số lượng theo định dạng:
Total items: [count]
Chương trình sẽ nhận đầu vào như sau:
- Dòng 1: Loại mặt hàng (
string,inthoặcpriced) - Dòng 2: Số lượng mặt hàng (số nguyên)
- Các dòng tiếp theo: Thông tin chi tiết của mặt hàng, tùy theo loại
Ví dụ, với đầu vào:
string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined PaperKết quả đầu ra sẽ là:
Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3Với đầu vào:
int
2
Apples
50
Oranges
30Kết quả đầu ra sẽ là:
Apples: 50
Oranges: 30
Total items: 2Với đầu vào:
priced
2
Widget
10
19.99
Gadget
5
49.50Kết quả đầu ra sẽ là:
Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2Lưu ý rằng cùng một cấu trúc Container có thể hoạt động liền mạch với cả chuỗi lẫn số nguyên, trong khi PricedItem dùng ràng buộc để đảm bảo chỉ có thể dùng kiểu số cho giá. Bạn phải chỉ định rõ tham số kiểu khi tạo instance của các struct tổng quát này.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Đọc kiểu dữ liệu của mục
scanner.Scan()
itemType := scanner.Text()
// Đọc số lượng
scanner.Scan()
count, _ := strconv.Atoi(scanner.Text())
switch itemType {
case "string":
// TODO: Tạo Container[string] bằng NewContainer
// TODO: Đọc 'count' mục, mỗi mục có tên ở một dòng và dữ liệu ở dòng tiếp theo
// TODO: Thêm từng mục vào vùng chứa
// TODO: In từng mục theo dạng "[Name]: [Data]"
// TODO: Dùng phương thức (method) Count để in "Total items: [count]"
case "int":
// TODO: Tạo Container[int] bằng NewContainer
// TODO: Đọc 'count' mục, mỗi mục có tên ở một dòng và dữ liệu số nguyên ở dòng tiếp theo
// TODO: Thêm từng mục vào vùng chứa
// TODO: In từng mục theo dạng "[Name]: [Data]"
// TODO: Dùng phương thức (method) Count để in "Total items: [count]"
case "priced":
// TODO: Tạo một lát cắt (slice) để chứa các instance PricedItem[float64]
// TODO: Đọc 'count' mục, mỗi mục có tên, số lượng và giá trên các dòng riêng
// TODO: Tạo PricedItem[float64] cho từng mục rồi thêm vào lát cắt
// TODO: In từng mục theo dạng "[Name] x[Quantity] @ [Price]", với giá hiển thị một chữ số thập phân
// TODO: In "Total items: [count]"
}
_ = scanner // Dùng scanner để đọc đầu vào
_ = count // Dùng count cho vòng lặp
}
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 poolTự luyện tập: Trình biên dịch Go online