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")
endCá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ònVò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.
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 raEmailing {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 raTexting {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 raPushing 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:
- Người nhận email (ví dụ:
alice@example.com) - Số điện thoại (ví dụ:
555-1234) - 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_42Hã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()
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
1Khái niệm “self”
Bảng với hàm“self” tường minhCú pháp dấu hai chấmDấu chấm và dấu hai chấmÔn tập: Điểm chuyển động4Dự án: Ngân hàng số
Thiết lập dự ánPhương thức nạp tiền7Đa hình và ghi đè
Ghi đè phương thứcGọi phương thức của lớp chaGõ kiểu vịtGiao diện chungKiểm tra kiểuÔn tập: Vai trò của nhân viên2Mẫu Prototype cho lớp
Khái niệm PrototypeLiên kết bằng __indexHàm khởi tạo :new()Khởi tạo thuộc tínhCác instance độc lậpÔn tập: Nhà máy ô tô5Nạp chồng toán tử trong OOP
Cộng các đối tượngTrừ các đối tượngNối các đối tượngSo sánh các đối tượng (<, >)Ôn tập: phép tính với ví tiền8Đóng gói
Quy ước đặt tênClosure để bảo mậtTruy cập qua closureBảng chỉ đọcLogic kiểm tra hợp lệTổng kết: Két an toàn11Mẫu thiết kế (cơ bản)
Hàm tạo đối tượngBảng SingletonMẫu IteratorObserver (Listener)Ôn tập: Factory Logger3Trạng thái và hành vi của đối tượng
Biến instancePhương thức getterPhương thức setterThuộc tính tính toánĐịnh dạng chuỗiKiểm tra tính bằng nhauÔn tập: điểm số học sinh6Cơ bản về kế thừa
Thiết lập kế thừaKế thừa phương thứcMở rộng hàm khởi tạoThêm phương thức cho lớp conDùng chung và riêngÔn tập: hệ phân cấp hình dạngTự luyện tập: Trình biên dịch Lua online