Menu
CoddyTech

Gõ kiểu vịt

Thuộc phần Lập trình hướng đối tượng trong hành trình Lua của Coddy. Bài học 39 trên 70.

Lua không quan tâm đến lớp của một đối tượng. Lua chỉ quan tâm đối tượng đó có thể làm gì. Khái niệm này gọi là định kiểu vịt (duck typing): “Nếu nó đi như vịt và kêu như vịt, thì nó là vịt.” Theo cách nói trong lập trình, nếu một đối tượng có phương thức mà bạn đang gọi, thì phương thức đó sẽ hoạt động.

Điều này có nghĩa là bạn có thể kết hợp các lớp hoàn toàn không liên quan với nhau, miễn là chúng có cùng tên phương thức:

local Circle = {}
Circle.__index = Circle
function Circle:new() 
    local obj = {}
    setmetatable(obj, Circle)
    return obj
end
function Circle:draw()
    print("Drawing a circle")
end

local Square = {}
Square.__index = Square
function Square:new()
    local obj = {}
    setmetatable(obj, Square)
    return obj
end
function Square:draw()
    print("Drawing a square")
end

Các lớp này không có quan hệ kế thừa: chúng hoàn toàn độc lập. Tuy vậy, ta có thể xử lý chúng theo cùng một cách:

local shapes = {Circle:new(), Square:new(), Circle:new()}

for _, shape in ipairs(shapes) do
    shape:draw()
end
-- Đầu ra:
-- Vẽ một hình tròn
-- Vẽ một hình vuông
-- Vẽ một hình tròn

Vòng lặp không kiểm tra kiểu của từng đối tượng. Nó chỉ gọi :draw() và tin rằng phương thức này tồn tại. Sự linh hoạt này rất hữu ích. Bạn có thể thêm các kiểu hình mới mà không cần sửa đổi mã vòng lặp. Miễn là lớp mới triển khai :draw(), mọi thứ sẽ hoạt động.

challenge icon

Thử thách

Dễ

Hãy cùng thể hiện sức mạnh của kiểu gõ vịt bằng cách xây dựng một hệ thống thông báo với các lớp hoàn toàn không liên quan nhưng đều có thể được xử lý theo cùng một cách!

Bạn sẽ tạo ba lớp thông báo độc lập, không lớp nào kế thừa lớp nào, nhưng tất cả đều triển khai phương thức :send(). Nhờ vậy, bạn có thể lặp qua một tập hợp gồm nhiều loại đối tượng và gọi :send() cho từng đối tượng mà không cần quan tâm đến kiểu thực tế của nó.

Bạn sẽ sắp xếp code trong bốn tệp:

  • Email.lua: Một lớp có hàm khởi tạo :new(recipient) để lưu người nhận email. Phương thức :send() sẽ in ra Emailing {recipient}.
  • SMS.lua: Một lớp hoàn toàn riêng biệt có hàm khởi tạo :new(phoneNumber). Phương thức :send() sẽ in ra Texting {phoneNumber}.
  • PushNotification.lua: Một lớp độc lập khác có hàm khởi tạo :new(deviceId). Phương thức :send() sẽ in ra Pushing to {deviceId}.
  • main.lua: Kết hợp mọi thứ! Đọc ba đầu vào, tạo một instance cho mỗi loại thông báo, đưa tất cả vào cùng một bảng, rồi lặp qua bảng và gọi :send() cho từng đối tượng.

Bạn sẽ nhận ba đầu vào:

  1. Người nhận email (ví dụ: alice@example.com)
  2. Số điện thoại (ví dụ: 555-1234)
  3. ID thiết bị (ví dụ: device_99)

Trong tệp chính, hãy tạo cả ba đối tượng thông báo, lưu chúng vào một bảng rồi lặp qua bảng và gọi :send() cho từng đối tượng. Thứ tự là: Email trước, tiếp theo là SMS, rồi đến PushNotification.

Ví dụ, nếu đầu vào là bob@mail.com, 555-9876 và phone_42, đầu ra sẽ là:

Emailing bob@mail.com
Texting 555-9876
Pushing to phone_42

Hãy chú ý rằng vòng lặp không cần kiểm tra kiểu của từng đối tượng. Nó chỉ cần tin rằng mọi đối tượng trong tập hợp đều có phương thức :send(). Đó chính là kiểu gõ vịt! Nếu đối tượng có thể gọi :send(), thì đó là một thông báo.

Tự làm thử

-- main.lua
-- Tập hợp các loại thông báo và minh họa duck typing

-- Nhập các lớp thông báo
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')

-- Đọc dữ liệu đầu vào
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()

-- TODO: Tạo một instance cho mỗi loại thông báo

-- TODO: Đưa tất cả thông báo vào một bảng (Email trước, rồi đến SMS và PushNotification)

-- TODO: Duyệt qua bảng và gọi :send() trên từng thông báo
-- Đây là cách minh họa duck typing: không kiểm tra kiểu, chỉ gọi :send()
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 Lua online