Menu
CoddyTech

Bảng Singleton

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

Đôi khi, bạn cần chỉ có một đối tượng của một thứ trong toàn bộ chương trình, chẳng hạn như trình quản lý cấu hình, hệ thống ghi nhật ký hoặc cài đặt trò chơi. Tạo nhiều bản sao sẽ gây ra vấn đề: bản nào có dữ liệu chính xác? Mẫu Singleton đảm bảo chỉ có một đối tượng tồn tại.

Trong Lua, việc triển khai Singleton đơn giản đến bất ngờ nhờ cách module hoạt động. Khi bạn gọi require để nạp một module, Lua lưu kết quả vào bộ nhớ đệm. Những lần gọi require sau đó sẽ trả về bảng đã lưu trong bộ nhớ đệm, chứ không phải một bảng mới.

-- GameConfig.lua
local GameConfig = {
    volume = 80,
    difficulty = "normal"
}

function GameConfig:setVolume(v)
    self.volume = v
end

return GameConfig

Bây giờ, ở bất cứ đâu trong chương trình của bạn:

local config1 = require("GameConfig")
local config2 = require("GameConfig")

config1:setVolume(50)
print(config2.volume)  -- 50 (cùng một bảng!)

Cả config1 và config2 đều tham chiếu đến cùng một bảng. Các thay đổi được thực hiện thông qua biến này sẽ hiển thị qua biến kia. Điều này tự động xảy ra vì require của Lua lưu giá trị trả về trong package.loaded rồi dùng lại.

Khác với mẫu Factory tạo đối tượng mới, Singleton đảm bảo trạng thái dùng chung trong toàn bộ ứng dụng. Đây là lựa chọn lý tưởng cho các thiết lập toàn cục mà nhiều phần trong code cần truy cập và chỉnh sửa một cách nhất quán.

challenge icon

Thử thách

Dễ

Hãy tạo một trình quản lý cài đặt trò chơi bằng mẫu Singleton! Bạn sẽ tạo một module cấu hình để mọi phần trong chương trình đều truy cập cùng một bộ cài đặt, bất kể module được yêu cầu bao nhiêu lần.

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

  • GameSettings.lua: Tạo một module Singleton để lưu cấu hình trò chơi. Bảng cài đặt cần có musicVolume (bắt đầu ở mức 70), sfxVolume (bắt đầu ở mức 100) và difficulty (bắt đầu với giá trị "normal"). Thêm ba phương thức: :setMusicVolume(value), :setSfxVolume(value) và :setDifficulty(level) để thay đổi các cài đặt này. Nhớ trả về bảng ở cuối module!
  • main.lua: Chứng minh Singleton hoạt động bằng cách yêu cầu module GameSettings hai lần và gán vào hai biến khác nhau. Dùng biến thứ nhất để thay đổi âm lượng nhạc và độ khó. Sau đó, dùng biến thứ hai để in ra cả ba cài đặt. Nếu Singleton hoạt động đúng, cả hai biến đều tham chiếu đến cùng một bảng, vì vậy các thay đổi qua biến này sẽ hiển thị qua biến kia.

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

  1. Âm lượng nhạc mới (một số)
  2. Mức độ khó mới (một chuỗi như "easy" hoặc "hard")

Sau khi thay đổi cài đặt qua biến thứ nhất, hãy dùng biến thứ hai để in ra ba cài đặt, mỗi cài đặt trên một dòng riêng theo thứ tự sau: âm lượng nhạc, âm lượng hiệu ứng âm thanh, rồi đến độ khó.

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

50
100
hard

Nếu đầu vào là 25 và easy, đầu ra sẽ là:

25
100
easy

Lưu ý rằng âm lượng hiệu ứng âm thanh vẫn giữ giá trị mặc định là 100 vì ta chưa thay đổi nó, còn âm lượng nhạc và độ khó phản ánh những thay đổi qua biến thứ nhất. Điều này chứng minh cả hai biến đều trỏ đến cùng một đối tượng Singleton!

Tự làm thử

-- main.lua: Minh họa mẫu Singleton

-- Đọc đầu vào
local newMusicVolume = tonumber(io.read())
local newDifficulty = io.read()

-- TODO: Nạp module GameSettings vào một biến (ví dụ: settings1)

-- TODO: Nạp LẠI module GameSettings vào một biến KHÁC (ví dụ: settings2)
-- (Nếu Singleton hoạt động đúng, cả hai biến cùng tham chiếu đến một bảng!)

-- TODO: Dùng biến THỨ NHẤT để:
--   - Đặt âm lượng nhạc thành newMusicVolume
--   - Đặt độ khó thành newDifficulty

-- TODO: Dùng biến THỨ HAI để in ra các thiết lập
-- In mỗi giá trị trên một dòng riêng: musicVolume, sfxVolume, difficulty
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