Menu
CoddyTech

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.

challenge icon

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 LightController có trường Brightness (int) và phương thức SetLight(level int) string trả về Light set to [level]%
    • Một AudioController có trường Volume (int) và phương thức SetVolume(level int) string trả về Volume set to [level]%
    Cả hai bộ điều khiển cũng cần có phương thức 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]%.
  • device.go: Tạo một struct SmartDevice có trường Name và nhúng cả LightController lẫn AudioController. 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ức Status() 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ột SmartDevice, 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()
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch Go online