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
endHã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: ToyotaMẫ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.
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__indexvà 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ằngAnimal.new(Animal, name)để khởi tạo tên, sau đó thêm trườngwingspanvà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:
- Tên của con chim
- 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)
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ề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