Các tập hợp đa hình
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 44 trên 107.
Một trong những ứng dụng mạnh mẽ nhất của interface là lưu trữ các kiểu dữ liệu khác nhau trong cùng một tập hợp. Một slice có kiểu interface có thể chứa mọi giá trị đáp ứng interface đó, giúp bạn nhóm các kiểu dữ liệu có liên quan nhưng khác nhau lại với nhau.
Hãy hình dung một tình huống bạn cần quản lý nhiều hình dạng khác nhau. Thay vì dùng các lát cắt riêng cho từng kiểu, bạn có thể dùng một lát cắt duy nhất có kiểu giao diện:
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
type Rectangle struct{ Width, Height float64 }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 3},
Circle{Radius: 2},
}
for _, s := range shapes {
fmt.Printf("Area: %.2f\n", s.Area())
}
}
Mảng con shapes chứa cả hình tròn lẫn hình chữ nhật. Khi lặp qua mảng, mỗi phần tử phản hồi với Area() tùy theo kiểu thực tế của nó. Đây là tính đa hình áp dụng cho tập hợp: cùng một vòng lặp xử lý đồng nhất mọi kiểu hình.
Các tập hợp đa hình rất cần thiết khi xây dựng hệ thống xử lý nhiều loại phần tử khác nhau: một hệ thống thông báo gửi email và tin nhắn SMS, một trò chơi cập nhật nhiều loại kẻ địch hoặc một trình xử lý tài liệu hỗ trợ nhiều định dạng tệp. Tập hợp không quan tâm đến từng kiểu cụ thể, mà chỉ cần mỗi phần tử cung cấp hành vi cần thiết.
Thử thách
DễHãy xây dựng một hệ thống quản lý công việc xử lý nhiều loại công việc thông qua một giao diện thống nhất. Bạn sẽ tạo nhiều loại công việc khác nhau rồi xử lý tất cả cùng nhau trong một tập hợp đa hình.
Bạn sẽ sắp xếp code trong ba tệp:
task.go: Định nghĩa giao diệnTask, yêu cầu có phương thứcSummary() string. Giao diện này sẽ là hợp đồng chung mà tất cả các loại công việc phải tuân theo.types.go: Tạo ba loại công việc khác nhau, mỗi loại đều triển khai giao diệnTask:BugFixcó các trườngID(string) vàSeverity(string). Phương thứcSummary()trả vềBug #[ID] ([Severity])Featurecó các trườngName(string) vàPoints(int). Phương thứcSummary()trả vềFeature: [Name] - [Points] ptsDocumentationcó trườngTopic(string). Phương thứcSummary()trả vềDocs: [Topic]
main.go: Tạo hàm có tênPrintBacklog, nhận một lát cắtTaskvà in phần tóm tắt của từng công việc trên một dòng riêng. Đọc thông tin công việc từ đầu vào, tạo một đối tượng cho mỗi loại công việc, tập hợp tất cả vào một lát cắt[]Taskrồi truyền lát cắt đó choPrintBacklog.
Đầu vào sẽ gồm các dòng sau:
- Dòng 1: ID của bug
- Dòng 2: Mức độ nghiêm trọng của bug
- Dòng 3: Tên feature
- Dòng 4: Số điểm của feature (số nguyên)
- Dòng 5: Chủ đề tài liệu
Ví dụ, với các giá trị 1042, critical, Dark Mode, 8 và API Reference, đầu ra sẽ là:
Bug #1042 (critical)
Feature: Dark Mode - 8 pts
Docs: API ReferenceĐiểm mạnh ở đây là PrintBacklog không cần logic riêng cho bug, feature hay tài liệu. Hàm chỉ cần duyệt qua lát cắt và gọi Summary() trên từng phần tử. Mỗi loại công việc trả về định dạng riêng, cho thấy các tập hợp đa hình giúp bạn xử lý đồng nhất nhiều loại khác nhau như thế nào.
Tự làm thử
package main
import (
"fmt"
)
// TODO: Tạo hàm PrintBacklog nhận một slice chứa các Task
// và in phần tóm tắt của từng tác vụ trên một dòng riêng
func main() {
// Đọc đầu vào
var bugID string
var bugSeverity string
var featureName string
var featurePoints int
var docTopic string
fmt.Scanln(&bugID)
fmt.Scanln(&bugSeverity)
fmt.Scanln(&featureName)
fmt.Scanln(&featurePoints)
fmt.Scanln(&docTopic)
// TODO: Tạo một tác vụ cho mỗi loại (BugFix, Feature, Documentation)
// TODO: Gom tất cả tác vụ vào một slice []Task
// TODO: Gọi PrintBacklog với slice chứa các tác vụ
}
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