Tự động nâng phương thức
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 36 trên 107.
Cũng như việc nhúng struct đưa các trường lên struct bên ngoài, Go cũng đưa các phương thức từ kiểu được nhúng lên. Điều này có nghĩa là bạn có thể gọi trực tiếp trên struct bên ngoài các phương thức được định nghĩa trong struct được nhúng.
type Engine struct {
Horsepower int
}
func (e Engine) Start() string {
return "Engine started"
}
type Car struct {
Model string
Engine // nhúng
}
Vì Car nhúng Engine, phương thức Start() tự động được nâng lên:
func main() {
c := Car{
Model: "Sedan",
Engine: Engine{Horsepower: 200},
}
fmt.Println(c.Start()) // Engine đã khởi động
fmt.Println(c.Engine.Start()) // Cũng hoạt động
}
Việc nâng cấp này có một hệ quả quan trọng đối với các giao diện. Nếu kiểu được nhúng thỏa mãn một giao diện, kiểu bên ngoài cũng tự động thỏa mãn giao diện đó:
type Starter interface {
Start() string
}
func Ignite(s Starter) {
fmt.Println(s.Start())
}
func main() {
c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
Ignite(c) // Car đáp ứng giao diện Starter thông qua Engine
}
Kiểu Car không triển khai tường minh Starter, nhưng vẫn đáp ứng giao diện này vì Engine được nhúng trong nó có phương thức cần thiết. Đây là cách Go tái sử dụng hành vi mà không cần kế thừa truyền thống.
Thử thách
DễHãy xây dựng một hệ thống phát nhạc để minh họa cách các phương thức từ kiểu được nhúng được nâng lên kiểu bên ngoài, và cách điều này cho phép đáp ứng giao diện thông qua kết hợp.
Bạn sẽ sắp xếp code trong hai tệp:
audio.go: Tạo các khối cấu thành cho trình phát nhạc:- Một cấu trúc
AudioPlayercó trườngBrand(chuỗi) và phương thứcPlay() stringtrả về[Brand] playing audio - Một giao diện
Playableyêu cầu phương thứcPlay() string - Một cấu trúc
Smartphonecó trườngModelvà nhúngAudioPlayer - Một cấu trúc
Tabletcó trườngModelvà nhúngAudioPlayer
- Một cấu trúc
main.go: Tạo hàm có tênStartPlaybacknhận bất kỳPlayablenào và trả về kết quả của việc gọiPlay(). Đọc thông tin thiết bị từ đầu vào, tạo cả một Smartphone và một Tablet, rồi minh họa rằng cả hai đều đáp ứng giao diệnPlayablethông quaAudioPlayerđược nhúng, dù không kiểu nào tự triển khai rõ ràngPlay().
Đầu vào sẽ gồm:
- Dòng 1: Tên mẫu Smartphone
- Dòng 2: Thương hiệu âm thanh của Smartphone
- Dòng 3: Tên mẫu Tablet
- Dòng 4: Thương hiệu âm thanh của Tablet
Với mỗi thiết bị, hãy in mẫu thiết bị, sau đó là kết quả khi truyền thiết bị đó vào StartPlayback.
Ví dụ, với iPhone 15, Apple Audio, iPad Pro và Beats, đầu ra của bạn sẽ là:
iPhone 15
Apple Audio playing audio
iPad Pro
Beats playing audioHãy chú ý rằng bạn có thể truyền cả Smartphone lẫn Tablet vào StartPlayback vì phương thức Play() từ AudioPlayer tự động được nâng lên từng kiểu bên ngoài, giúp đáp ứng giao diện Playable mà không cần thêm code.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Tạo hàm (function) StartPlayback nhận bất kỳ Playable nào
// và trả về kết quả khi gọi Play()
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Đọc tên mẫu điện thoại thông minh
scanner.Scan()
smartphoneModel := scanner.Text()
// Đọc tên thương hiệu âm thanh của điện thoại thông minh
scanner.Scan()
smartphoneBrand := scanner.Text()
// Đọc tên mẫu máy tính bảng
scanner.Scan()
tabletModel := scanner.Text()
// Đọc tên thương hiệu âm thanh của máy tính bảng
scanner.Scan()
tabletBrand := scanner.Text()
// TODO: Tạo Smartphone có AudioPlayer được nhúng
// TODO: Tạo Tablet có AudioPlayer được nhúng
// TODO: In ra tên mẫu điện thoại thông minh, rồi gọi StartPlayback với điện thoại đó
// TODO: In ra tên mẫu máy tính bảng, rồi gọi StartPlayback với máy tính bảng đó
_ = smartphoneModel
_ = smartphoneBrand
_ = tabletModel
_ = tabletBrand
fmt.Println("TODO: Complete the implementation")
}
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