Menu
CoddyTech

Đị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.

challenge icon

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:
    • Phone có trường Number. Phương thức SendMessage(text string) string trả về SMS to [Number]: [text]
    • Computer có trường Email. Phương thức SendMessage(text string) string trả về Email to [Email]: [text]
    • Pigeon có trường Name. Phương thức SendMessage(text string) string trả về [Name] carries: [text]
    Lưu ý rằng không kiểu nào trong số này nhắc đến giao diện. Chúng chỉ tình cờ có cùng chữ ký phương thức.
  • sender.go: Định nghĩa giao diện Messenger, yêu cầu một phương thức SendMessage(text string) string. Tạo một hàm có tên Broadcast, nhận một lát cắt Messenger và 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ọi SendMessage trê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ắt Messenger và dùng Broadcast để 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
}
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