Nhập môn nhúng struct
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 35 trên 107.
Nhúng struct là cơ chế của Go cho phép đưa một kiểu struct vào bên trong một kiểu struct khác. Thay vì dùng tên trường, bạn chỉ cần chỉ định kiểu, rồi Go đưa các trường của struct được nhúng lên struct bên ngoài.
Đây là cú pháp cơ bản:
type Address struct {
City string
Country string
}
type Person struct {
Name string
Address // nhúng trực tiếp, không có tên trường
}
Khi nhúng Address vào Person, bạn có thể truy cập trực tiếp các trường được nhúng thông qua struct bên ngoài:
func main() {
p := Person{
Name: "Alice",
Address: Address{
City: "London",
Country: "UK",
},
}
fmt.Println(p.City) // London: truy cập trực tiếp
fmt.Println(p.Country) // UK: truy cập trực tiếp
fmt.Println(p.Address.City) // cũng hoạt động
}
Hãy để ý p.City vẫn hoạt động dù không cần viết rõ p.Address.City. Go tự động đưa các trường của cấu trúc nhúng lên cấu trúc bên ngoài, giúp bạn truy cập chúng như thể chúng thuộc về cấu trúc bên ngoài. Khi cần, bạn vẫn có thể truy cập chúng thông qua tên kiểu được nhúng.
Cách này khác với các struct lồng nhau thông thường, nơi bạn luôn phải dùng tên trường để truy cập các trường bên trong. Nhúng tạo ra API phẳng hơn, tiện lợi hơn mà vẫn giữ được cách nhóm hợp lý các dữ liệu liên quan.
Thử thách
DễHãy xây dựng một hệ thống đăng ký xe bằng cách nhúng struct để tạo mô hình dữ liệu gọn gàng, có tổ chức. Bạn sẽ tách thông tin liên hệ khỏi thông tin chi tiết của xe, đồng thời vẫn có thể truy cập trực tiếp mọi trường thông qua tính năng nhúng.
Bạn sẽ sắp xếp code trong hai tệp:
vehicle.go: Tạo hai struct phối hợp với nhau thông qua tính năng nhúng:- Một struct
ContactInfocó các trườngOwnerNamevàPhone(cả hai đều là chuỗi) - Một struct
VehiclenhúngContactInfo(không có tên trường) và bổ sung các trườngMake,ModelvàYear
SummarychoVehicle. Phương thức này trả về một chuỗi đã định dạng, chứa mọi thông tin về xe và thông tin liên hệ.- Một struct
main.go: Đọc thông tin về xe và chủ xe từ đầu vào, tạo một instanceVehiclecó nhúngContactInfo, rồi minh họa cách truy cập các trường được nhúng trực tiếp và thông qua tên kiểu được nhúng. Cuối cùng, in phần tóm tắt về xe.
Đầu vào sẽ gồm những dòng sau:
- Dòng 1: Tên chủ xe
- Dòng 2: Số điện thoại
- Dòng 3: Hãng xe
- Dòng 4: Mẫu xe
- Dòng 5: Năm sản xuất (số nguyên)
Phương thức Summary cần trả về một chuỗi theo định dạng sau:
[Year] [Make] [Model] - Owner: [OwnerName], Phone: [Phone]Trong tệp chính, hãy minh họa việc nâng cấp trường bằng cách in ra:
- Tên chủ xe được truy cập trực tiếp trên Vehicle (ví dụ:
v.OwnerName) - Số điện thoại được truy cập thông qua kiểu được nhúng (ví dụ:
v.ContactInfo.Phone) - Phần tóm tắt đầy đủ từ phương thức
Summary
Ví dụ, với đầu vào gồm Alice Smith, 555-1234, Toyota, Camry và 2023, đầu ra sẽ là:
Alice Smith
555-1234
2023 Toyota Camry - Owner: Alice Smith, Phone: 555-1234Hãy chú ý cách cả hai cách truy cập đều hoạt động: truy cập trực tiếp thông qua việc nâng cấp trường và truy cập tường minh thông qua tên kiểu được nhúng. Nhờ đó, bạn có thể linh hoạt khi làm việc với dữ liệu được nhúng.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Đọc tên chủ sở hữu
ownerName, _ := reader.ReadString('\n')
ownerName = strings.TrimSpace(ownerName)
// Đọc số điện thoại
phone, _ := reader.ReadString('\n')
phone = strings.TrimSpace(phone)
// Đọc hãng xe
make, _ := reader.ReadString('\n')
make = strings.TrimSpace(make)
// Đọc mẫu xe
model, _ := reader.ReadString('\n')
model = strings.TrimSpace(model)
// Đọc năm
yearStr, _ := reader.ReadString('\n')
yearStr = strings.TrimSpace(yearStr)
year, _ := strconv.Atoi(yearStr)
// TODO: Tạo một instance Vehicle có ContactInfo được nhúng
// Dùng các giá trị đã đọc từ đầu vào ở trên
// TODO: In ra tên chủ sở hữu bằng cách truy cập trực tiếp qua Vehicle (thăng cấp trường)
// Ví dụ: fmt.Println(v.OwnerName)
// TODO: In ra số điện thoại bằng cách truy cập qua tên kiểu dữ liệu được nhúng
// Ví dụ: fmt.Println(v.ContactInfo.Phone)
// TODO: In ra bản tóm tắt đầy đủ bằng phương thức Summary
// Ví dụ: fmt.Println(v.Summary())
// Xóa các dòng này sau khi bạn hoàn thành lời giải
_ = ownerName
_ = phone
_ = make
_ = model
_ = year
}
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