Định kiểu vịt trong Go
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 42 trên 107.
Thuật ngữ duck typing bắt nguồn từ câu nói: "Nếu nó đi như vịt và kêu như vịt, thì nó là vịt." Trong Go, điều này có nghĩa là một kiểu không cần khai báo rõ ràng rằng nó triển khai một interface. Nó chỉ cần có các phương thức phù hợp.
Hãy xem xét giao diện này và hai kiểu hoàn toàn không liên quan:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Cả Duck lẫn Robot đều không nhắc đến Quacker ở bất cứ đâu trong phần định nghĩa. Tuy vậy, cả hai đều đáp ứng giao diện chỉ vì chúng có phương thức Quack() với chữ ký chính xác:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Cơ chế đáp ứng ngầm này rất mạnh mẽ vì nó cho phép bạn định nghĩa các giao diện sau khi kiểu dữ liệu đã tồn tại. Bạn có thể tạo một giao diện tương thích với các kiểu dữ liệu từ những package bên ngoài mà không cần sửa mã nguồn của chúng. Các kiểu dữ liệu đó không cần biết về giao diện của bạn. Chúng chỉ cần hoạt động đúng.
Duck typing trong Go mang đến cho bạn sự linh hoạt của các ngôn ngữ động mà vẫn đảm bảo an toàn kiểu dữ liệu tại thời điểm biên dịch. Trình biên dịch xác minh rằng các kiểu dữ liệu thực sự có những phương thức cần thiết trước khi cho phép dùng chúng làm giá trị interface.
Thử thách
DễHãy xây dựng một hệ thống nhắn tin để minh họa cách định kiểu vịt (duck typing) hoạt động. Bạn sẽ tạo các kiểu hoàn toàn không liên quan nhưng đều có thể gửi tin nhắn thông qua một giao diện chung, mà không kiểu nào cần khai báo rõ ràng rằng nó triển khai giao diện đó.
Bạn sẽ sắp xếp code trong ba tệp:
messengers.go: Tạo ba struct không liên quan nhưng tình cờ có cùng hành vi:Phonecó trườngNumber. Phương thứcSendMessage(text string) stringtrả vềSMS to [Number]: [text]Computercó trườngEmail. Phương thứcSendMessage(text string) stringtrả vềEmail to [Email]: [text]Pigeoncó trườngName. Phương thứcSendMessage(text string) stringtrả về[Name] carries: [text]
sender.go: Định nghĩa giao diệnMessenger, yêu cầu một phương thứcSendMessage(text string) string. Tạo một hàm có tênBroadcast, nhận một lát cắtMessengervà một chuỗi tin nhắn, sau đó trả về một lát cắt các chuỗi chứa kết quả gọiSendMessagetrên từng messenger.main.go: Đọc thông tin từ đầu vào, tạo một đối tượng cho mỗi kiểu messenger, gom chúng vào một lát cắtMessengervà dùngBroadcastđể gửi tin nhắn qua tất cả các messenger. In từng kết quả trên một dòng riêng.
Chương trình sẽ nhận các đầu vào sau:
- Dòng 1: Số điện thoại
- Dòng 2: Địa chỉ email
- Dòng 3: Tên chim bồ câu
- Dòng 4: Tin nhắn cần gửi
Ví dụ, với các đầu vào 555-1234, alice@mail.com, Winston và Hello World, đầu ra của bạn sẽ là:
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello WorldĐiểm mấu chốt ở đây là Phone, Computer và Pigeon chẳng có điểm gì chung. Chúng không chia sẻ kiểu cơ sở và cũng không khai báo rõ ràng rằng chúng triển khai Messenger. Tuy vậy, vì tất cả đều “kêu” giống nhau (có phương thức tương ứng), Go cho phép dùng chúng làm các giá trị Messenger. Đó chính là cách định kiểu vịt hoạt động.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Đọc số điện thoại
scanner.Scan()
phoneNumber := scanner.Text()
// Đọc địa chỉ email
scanner.Scan()
email := scanner.Text()
// Đọc tên chim bồ câu
scanner.Scan()
pigeonName := scanner.Text()
// Đọc thông điệp cần phát đi
scanner.Scan()
message := scanner.Text()
// TODO: Tạo một đối tượng cho mỗi loại phương tiện gửi tin (Phone, Computer, Pigeon)
// TODO: Gom tất cả phương tiện gửi tin vào một slice kiểu Messenger
// TODO: Dùng Broadcast để gửi thông điệp qua tất cả phương tiện gửi tin
// TODO: In mỗi kết quả trên một dòng riêng
// Phần giữ chỗ để sử dụng các biến (xóa khi triển khai)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
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