Menu
CoddyTech

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)
end

Giờ đâ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 động

Hà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ó.

challenge icon

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 ra Charging {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 ra Transferring {amount} from account {accountId}.
  • main.lua: Tạo một hàm có tên handlePayment(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:

  1. Số thẻ tín dụng (ví dụ: 4532-1234-5678)
  2. Mã tài khoản ngân hàng (ví dụ: ACC-9876)
  3. 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
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