Áp dụng nhiều mixin
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 52 trên 70.
Một mixin thêm một nhóm hành vi vào lớp. Nhưng các đối tượng thực tế thường cần khả năng từ nhiều nguồn. Một đối tượng trong trò chơi có thể cần di chuyển xung quanh và hiển thị trên màn hình: hai mối quan tâm riêng biệt không thuộc cùng một mixin.
Giải pháp rất đơn giản: áp dụng nhiều mixin cho cùng một lớp. Mỗi mixin đóng góp các phương thức riêng, và lớp đó sẽ có tất cả các phương thức này:
local Movable = {}
function Movable:move(dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end
local Visible = {}
function Visible:show()
print("Showing at " .. self.x .. ", " .. self.y)
end
function Visible:hide()
print("Hidden")
endÁp dụng cả hai mixin cho lớp của bạn bằng cùng hàm trợ giúp đã dùng trước đó:
local function applyMixin(class, mixin)
for key, value in pairs(mixin) do
class[key] = value
end
end
local GameObject = {}
GameObject.__index = GameObject
applyMixin(GameObject, Movable)
applyMixin(GameObject, Visible)Giờ đây, các đối tượng GameObject có thể vừa di chuyển vừa hiển thị chính mình:
local obj = setmetatable({x = 0, y = 0}, GameObject)
obj:move(5, 3)
obj:show() -- Hiển thị tại 5, 3Lưu ý: nếu hai mixin định nghĩa phương thức trùng tên, mixin được áp dụng sau sẽ ghi đè mixin đầu tiên. Hãy đặt tên các phương thức trong mixin khác nhau để tránh hành vi ngoài dự kiến.
Thử thách
DễHãy xây dựng một lớp Character cho trò chơi, kết hợp nhiều khả năng bằng mixin! Nhân vật của bạn có thể vừa tấn công kẻ địch vừa tự hồi máu: hai hành vi riêng biệt đến từ hai mixin khác nhau.
Bạn sẽ sắp xếp code trong bốn tệp:
Attacker.lua: Tạo một mixin có phương thức:attack(targetName)để in ra{self.name} attacks {targetName} for {self.damage} damage!(giả sử đối tượng có các trườngnamevàdamage)Healer.lua: Tạo một mixin có phương thức:heal(amount)để cộng lượng hồi máu vàoself.healthvà in ra{self.name} heals for {amount}. Health: {newHealth}Character.lua: Tạo một lớp Character với hàm khởi tạo:new(name, health, damage)để lưu cả ba thuộc tính. Áp dụng cả hai mixin Attacker và Healer để mọi nhân vật đều có thể tấn công và hồi máu. Thêm phương thức:status()để in ra{name} - Health: {health}, Damage: {damage}main.lua: Viết hàm trợ giúpapplyMixintại đây. Đọc đầu vào gồm tên nhân vật, máu, sát thương, mục tiêu cần tấn công và lượng máu cần hồi. Tạo một Character, hiển thị trạng thái, cho nhân vật tấn công mục tiêu, tự hồi máu, rồi hiển thị lại trạng thái.
Bạn sẽ nhận năm đầu vào:
- Tên nhân vật (một chuỗi)
- Lượng máu ban đầu (một số)
- Giá trị sát thương (một số)
- Tên mục tiêu cần tấn công (một chuỗi)
- Lượng máu cần hồi (một số)
Đầu ra cần cho thấy nhân vật sử dụng cả hai khả năng từ mixin:
{name} - Health: {health}, Damage: {damage}
{name} attacks {target} for {damage} damage!
{name} heals for {healAmount}. Health: {newHealth}
{name} - Health: {newHealth}, Damage: {damage}Ví dụ, nếu đầu vào là Warrior, 100, 25, Goblin và 30, đầu ra sẽ là:
Warrior - Health: 100, Damage: 25
Warrior attacks Goblin for 25 damage!
Warrior heals for 30. Health: 130
Warrior - Health: 130, Damage: 25Mỗi mixin bổ sung phương thức riêng vào lớp Character. Nhân vật không kế thừa từ mixin nào. Thay vào đó, nhân vật có được các khả năng của chúng thông qua hàm applyMixin. Đó chính là sức mạnh của việc kết hợp nhiều mixin!
Tự làm thử
-- Tệp chính
-- TODO: Viết hàm hỗ trợ applyMixin
-- Hàm này cần sao chép tất cả phương thức từ bảng mixin sang bảng đích
local function applyMixin(target, mixin)
-- TODO: Triển khai việc áp dụng mixin
end
-- Đưa applyMixin vào phạm vi toàn cục để Character.lua có thể sử dụng
_G.applyMixin = applyMixin
local Character = require('Character')
-- Đọc dữ liệu đầu vào
local name = io.read()
local health = tonumber(io.read())
local damage = tonumber(io.read())
local targetName = io.read()
local healAmount = tonumber(io.read())
-- TODO: Tạo một Character với tên, máu và sát thương đã cho
-- TODO: Hiển thị trạng thái của nhân vật
-- TODO: Cho nhân vật tấn công mục tiêu
-- TODO: Cho nhân vật tự hồi máu
-- TODO: Hiển thị lại trạng thái của nhân vật
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ạng9Kết hợp và mixin
Quan hệ có-mộtỦy quyềnMixin đơn giảnÁp dụng nhiều mixinMixin và kế thừaÔn tập: Lắp ráp robotTự luyện tập: Trình biên dịch Lua online