Menu
CoddyTech

Mở rộng hàm khởi tạo

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 33 trên 70.

Khi một lớp con kế thừa từ lớp cha, bạn thường muốn hàm khởi tạo của lớp con khởi tạo mọi thứ mà lớp cha sẽ khởi tạo, đồng thời thêm các trường riêng của lớp con. Thay vì lặp lại code, bạn có thể gọi trực tiếp hàm khởi tạo của lớp cha.

Kỹ thuật này dùng tên lớp cha để gọi phương thức :new() của lớp đó, truyền self làm đối số đầu tiên. Cách này tạo đối tượng cơ sở với đầy đủ phần khởi tạo của lớp cha, sau đó bạn tùy chỉnh đối tượng đó cho lớp con.

local Vehicle = {}
Vehicle.__index = Vehicle

function Vehicle:new(speed)
    local obj = {speed = speed}
    setmetatable(obj, Vehicle)
    return obj
end

-- Car kế thừa từ Vehicle
local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})

function Car:new(speed, brand)
    local obj = Vehicle.new(Vehicle, speed)  -- Gọi hàm khởi tạo của lớp cha
    obj.brand = brand                         -- Thêm trường riêng của lớp con
    setmetatable(obj, Car)                    -- Liên kết lại với Car
    return obj
end

Hãy chú ý đến các bước chính trong hàm khởi tạo của lớp con: đầu tiên, ta gọi Vehicle.new(Vehicle, speed) để tạo một đối tượng có trường speed được thiết lập sẵn. Sau đó, ta thêm trường brand dành riêng cho lớp con. Cuối cùng, ta đổi metatable từ Vehicle thành Car để instance tra cứu phương thức trong lớp con trước.

Bạn cũng có thể dùng cú pháp dấu hai chấm khi gọi hàm khởi tạo của lớp cha: Vehicle:new(speed) tương đương với Vehicle.new(Vehicle, speed), vì cả hai đều truyền Vehicle làm đối số đầu tiên. Cả hai cách đều hoạt động chính xác trong trường hợp này, nên bạn có thể dùng cách nào dễ đọc hơn.

local myCar = Car:new(120, "Toyota")
print(myCar.speed)  -- Đầu ra: 120
print(myCar.brand)  -- Đầu ra: Toyota

Mẫu này giúp code của bạn tuân theo nguyên tắc DRY: logic khởi tạo dùng chung nằm ở lớp cha, còn mỗi lớp con chỉ xử lý các thuộc tính riêng.

challenge icon

Thử thách

Dễ

Hãy xây dựng hệ thống phân cấp sinh vật, trong đó lớp con mở rộng hàm khởi tạo (constructor) của lớp cha! Bạn sẽ tạo lớp cha Animal và lớp con Bird. Hàm khởi tạo của Bird gọi hàm khởi tạo của Animal để xử lý phần khởi tạo chung trước khi thêm thuộc tính riêng.

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

  • Animal.lua: Định nghĩa lớp cha với hàm khởi tạo :new(name) để lưu tên của con vật. Thêm phương thức (method) :describe() để in tên con vật. Thiết lập mẫu lớp tiêu chuẩn với __index và trả về lớp.
  • Bird.lua: Định nghĩa lớp con kế thừa (inheritance) từ Animal. Hàm khởi tạo :new(name, wingspan) của Bird cần gọi hàm khởi tạo của lớp cha bằng Animal.new(Animal, name) để khởi tạo tên, sau đó thêm trường wingspan vào đối tượng, cuối cùng liên kết lại metatable với Bird. Thêm phương thức :info() để in cả tên lẫn sải cánh theo một định dạng cụ thể.
  • main.lua: Require module Bird, đọc hai đầu vào (tên và sải cánh), tạo một instance Bird, rồi gọi cả hai phương thức để minh họa cách tính năng kế thừa và tính năng riêng của lớp con hoạt động cùng nhau.

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

  1. Tên của con chim
  2. Sải cánh của con chim (một số)

Trong tệp main, hãy tạo một Bird với tên và sải cánh đã cho. Sau đó gọi :describe() rồi gọi :info(), mỗi phương thức trên một dòng riêng.

Phương thức :describe() (được kế thừa từ Animal) cần in:

Animal: {name}

Phương thức :info() (được định nghĩa trong Bird) cần in:

{name} has a wingspan of {wingspan}

Ví dụ: nếu đầu vào là Eagle và 200, đầu ra sẽ là:

Animal: Eagle
Eagle has a wingspan of 200

Điểm mấu chốt là hàm khởi tạo của Bird không lặp lại phần khởi tạo tên. Thay vào đó, nó giao phần việc đó cho hàm khởi tạo của Animal, rồi chỉ thêm những gì riêng có ở chim. Cách này giúp code của bạn tuân theo nguyên tắc DRY, đồng thời tạo nên chuỗi kế thừa phù hợp!

Tự làm thử

-- main.lua: Điểm bắt đầu

local Bird = require('Bird')

-- Đọc dữ liệu đầu vào
local name = io.read()
local wingspan = tonumber(io.read())

-- TODO: Tạo một instance Bird với tên và sải cánh đã cho

-- TODO: Gọi :describe() trên đối tượng chim (kế thừa từ Animal)

-- TODO: Gọi :info() trên đối tượng chim (định nghĩa trong Bird)
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