Giao diện chung
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 40 trên 70.
Duck typing cho thấy các lớp không liên quan vẫn có thể hoạt động cùng nhau nếu chúng có các phương thức cùng tên. Ý tưởng này dẫn đến một khái niệm thiết kế mạnh mẽ: giao diện chung. Khi bạn chủ ý thiết kế nhiều lớp có cùng chữ ký phương thức, bạn tạo ra các thành phần có thể thay thế cho nhau.
Hãy tưởng tượng bạn đang xây dựng một hệ thống ghi nhật ký. Bạn có thể muốn gửi thông báo đến nhiều nơi khác nhau: một tệp, màn hình hoặc thậm chí là mạng. Thay vì viết code riêng cho từng nơi, bạn thiết kế tất cả các lớp đầu ra để triển khai cùng một phương thức :write():
local LogFile = {}
LogFile.__index = LogFile
function LogFile:new(filename)
local obj = {filename = filename}
setmetatable(obj, LogFile)
return obj
end
function LogFile:write(message)
print("[FILE:" .. self.filename .. "] " .. message)
end
local Screen = {}
Screen.__index = Screen
function Screen:new()
local obj = {}
setmetatable(obj, Screen)
return obj
end
function Screen:write(message)
print("[SCREEN] " .. message)
endGiờ đây, mọi đoạn code cần in ra thông báo đều có thể chấp nhận một trong hai kiểu:
local function logMessage(output, msg)
output:write(msg)
end
local file = LogFile:new("app.log")
local screen = Screen:new()
logMessage(file, "Starting up") -- [FILE:app.log] Đang khởi động
logMessage(screen, "Starting up") -- [SCREEN] Đang khởi độngHàm (function) logMessage không biết và cũng không quan tâm nó nhận được lớp nào. Hàm chỉ cần phương thức :write(). Nhờ vậy, code của bạn linh hoạt hơn. Sau này, bạn có thể thêm các kiểu đầu ra mới mà không cần thay đổi những hàm hiện có.
Thử thách
DễHãy xây dựng một hệ thống xử lý thanh toán có thể dùng các phương thức thanh toán khác nhau thay thế cho nhau! Bạn sẽ tạo hai lớp thanh toán hoàn toàn không liên quan nhưng có chung một giao diện, phương thức :process(amount), cho phép mọi đoạn code xử lý thanh toán dùng được với cả hai loại mà không cần biết sự khác biệt.
Bạn sẽ sắp xếp code trong ba tệp:
CreditCard.lua: Một lớp thanh toán có hàm khởi tạo:new(cardNumber)để lưu số thẻ. Phương thức:process(amount)sẽ in raCharging {amount} to card {cardNumber}.BankTransfer.lua: Một lớp thanh toán hoàn toàn riêng biệt có hàm khởi tạo:new(accountId). Phương thức:process(amount)sẽ in raTransferring {amount} from account {accountId}.main.lua: Tạo một hàm có tênhandlePayment(paymentMethod, amount)nhận vào một đối tượng thanh toán và một khoản tiền, sau đó gọi:process(amount)trên đối tượng đó. Hàm này không cần biết nó đang xử lý thẻ tín dụng hay chuyển khoản ngân hàng. Hàm chỉ cần đối tượng có phương thức:process().
Bạn sẽ nhận ba đầu vào:
- Số thẻ tín dụng (ví dụ:
4532-1234-5678) - Mã tài khoản ngân hàng (ví dụ:
ACC-9876) - Khoản tiền thanh toán (ví dụ:
150)
Trong tệp chính, hãy tạo một đối tượng cho mỗi loại thanh toán bằng hai đầu vào đầu tiên. Sau đó, dùng hàm handlePayment để xử lý khoản tiền đã cho qua cả hai phương thức thanh toán: trước tiên là thẻ tín dụng, sau đó là chuyển khoản ngân hàng.
Ví dụ: nếu đầu vào là 1111-2222-3333, ACC-5555 và 75, đầu ra sẽ là:
Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555Điểm hay của thiết kế này là hàm handlePayment hoạt động với mọi đối tượng có phương thức :process(). Ngày mai, bạn có thể thêm lớp PayPal, và miễn là lớp đó có phương thức :process(amount), nó sẽ hoạt động với hàm hiện có mà không cần thay đổi gì!
Tự làm thử
-- Nhập các lớp thanh toán
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')
-- Đọc các đầu vào
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())
-- TODO: Tạo hàm (function) handlePayment nhận một đối tượng (object) thanh toán bất kỳ
-- và một số tiền, rồi gọi :process(amount) trên đối tượng đó
-- TODO: Tạo các instance của CreditCard và BankTransfer bằng các đầu vào
-- TODO: Dùng handlePayment để xử lý số tiền qua cả hai phương thức thanh toán
-- Trước tiên là thẻ tín dụng, sau đó là chuyển khoản ngân hàng
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