Nhúng nhiều 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 37 trên 107.
Go cho phép bạn nhúng nhiều struct vào một struct duy nhất, kết hợp các hành vi từ nhiều nguồn khác nhau. Cách này tương tự như kế thừa đa lớp trong các ngôn ngữ khác, nhưng sử dụng cách tiếp cận kết hợp của Go.
type Logger struct{}
func (l Logger) Log(msg string) string {
return "LOG: " + msg
}
type Notifier struct{}
func (n Notifier) Notify(msg string) string {
return "NOTIFY: " + msg
}
type Service struct {
Name string
Logger
Notifier
}
Giờ đây, struct Service có thể sử dụng các phương thức từ cả hai kiểu được nhúng:
func main() {
s := Service{Name: "OrderService"}
fmt.Println(s.Log("started")) // LOG: đã bắt đầu
fmt.Println(s.Notify("new order")) // NOTIFY: đơn hàng mới
}
Khi nhúng nhiều struct, có thể xảy ra xung đột tên nếu hai kiểu được nhúng có các phương thức trùng tên. Go không tự động giải quyết sự mơ hồ này:
type A struct{}
func (A) Greet() string { return "Hello from A" }
type B struct{}
func (B) Greet() string { return "Hello from B" }
type Combined struct {
A
B
}
func main() {
c := Combined{}
// c.Greet(): lỗi biên dịch do không rõ cần chọn phương thức nào
fmt.Println(c.A.Greet()) // Lời chào từ A
fmt.Println(c.B.Greet()) // Lời chào từ B
}
Khi xảy ra xung đột, bạn phải nêu rõ phương thức của kiểu được nhúng mà bạn muốn gọi bằng cách dùng tên kiểu làm định danh.
Thử thách
DễHãy xây dựng một hệ thống thiết bị nhà thông minh kết hợp nhiều khả năng thông qua cách nhúng struct. Bạn sẽ tạo một thiết bị có thể điều khiển đèn và phát nhạc bằng cách nhúng các kiểu bộ điều khiển riêng biệt.
Bạn sẽ sắp xếp code trong ba tệp:
controllers.go: Tạo hai struct bộ điều khiển độc lập, cung cấp các khả năng khác nhau:- Một
LightControllercó trườngBrightness(int) và phương thứcSetLight(level int) stringtrả vềLight set to [level]% - Một
AudioControllercó trườngVolume(int) và phương thứcSetVolume(level int) stringtrả vềVolume set to [level]%
Status() string. Điều này tạo ra xung đột tên mà bạn cần xử lý.LightController.Status()cần trả vềBrightness: [Brightness]%vàAudioController.Status()cần trả vềVolume: [Volume]%.- Một
device.go: Tạo một structSmartDevicecó trườngNamevà nhúng cảLightControllerlẫnAudioController. Thêm một phương thức tên làFullStatus() string, phương thức này trả về tên thiết bị cùng trạng thái của cả hai bộ điều khiển. Giải quyết sự mơ hồ bằng cách gọi tường minh phương thứcStatus()của từng kiểu được nhúng.main.go: Đọc cấu hình thiết bị từ đầu vào, tạo mộtSmartDevice, dùng các phương thức được kế thừa để đặt mức độ sáng và âm lượng, rồi hiển thị trạng thái đầy đủ.
Đầu vào sẽ gồm:
- Dòng 1: Tên thiết bị
- Dòng 2: Mức độ sáng ban đầu (số nguyên)
- Dòng 3: Mức âm lượng ban đầu (số nguyên)
- Dòng 4: Mức độ sáng mới cần đặt (số nguyên)
- Dòng 5: Mức âm lượng mới cần đặt (số nguyên)
Phương thức FullStatus() của bạn cần trả về:
[Name] - [LightController.Status()], [AudioController.Status()]In kết quả gọi SetLight và SetVolume (được kế thừa từ các kiểu được nhúng), sau đó in trạng thái đầy đủ.
Ví dụ, với đầu vào Living Room Hub, 50, 30, 75 và 60, đầu ra sẽ là:
Light set to 75%
Volume set to 60%
Living Room Hub - Brightness: 75%, Volume: 60%Lưu ý rằng bạn có thể gọi trực tiếp SetLight và SetVolume trên SmartDevice nhờ cơ chế kế thừa phương thức, nhưng cần ghi rõ tên kiểu vì cả hai kiểu được nhúng đều có phương thức Status().
Tự làm thử
package main
import (
"fmt"
)
func main() {
// Đọc đầu vào
var name string
fmt.Scanln(&name)
var initialBrightness int
fmt.Scanln(&initialBrightness)
var initialVolume int
fmt.Scanln(&initialVolume)
var newBrightness int
fmt.Scanln(&newBrightness)
var newVolume int
fmt.Scanln(&newVolume)
// TODO: Tạo SmartDevice với tên và các giá trị ban đầu đã cho
// TODO: Dùng phương thức SetLight được kế thừa và in ra kết quả
// TODO: Dùng phương thức SetVolume được kế thừa và in ra kết quả
// TODO: In ra toàn bộ trạng thái bằng phương thức FullStatus()
}
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