Menu
CoddyTech

Closure để bảo mật

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

Quy ước đặt tên giúp thể hiện mục đích, nhưng thực sự không ngăn được việc truy cập. Bất kỳ ai vẫn có thể viết player._health = 999 và đoạn code vẫn chạy. Để có tính riêng tư thực sự, Lua có một cách tiếp cận khác: lưu dữ liệu trong các biến cục bộ bên trong hàm khởi tạo thay vì trên self.

Điểm mấu chốt là các biến cục bộ bên trong một hàm chỉ có thể được truy cập trong hàm đó và trong bất kỳ hàm nào được định nghĩa bên trong nó. Đây được gọi là bao đóng:

local Counter = {}
Counter.__index = Counter

function Counter:new()
    local count = 0  -- Thực sự riêng tư! Không nằm trên self.
    
    local obj = {}
    setmetatable(obj, Counter)
    return obj
end

Trong ví dụ này, count chỉ tồn tại bên trong hàm :new(). Nó không được lưu trong obj, nên bạn không thể truy cập nó từ bên ngoài: myCounter.count trả về nil, và cũng không có trường nào bắt đầu bằng dấu gạch dưới để truy cập vòng.

Biến count nằm trong phạm vi của closure. Nó hoàn toàn ẩn với code bên ngoài. (Như đoạn code hiện tại, chưa có gì lưu giữ count; trong bài học tiếp theo, bạn sẽ thêm các phương thức bên trong hàm khởi tạo để tạo closure bao quanh biến này, nhờ đó nó tồn tại chừng nào đối tượng còn tồn tại.) Khác với self._count, vốn chỉ là một lời đề nghị lịch sự, cách này khiến dữ liệu thực sự không thể truy cập được.

Tất nhiên, dữ liệu ẩn sẽ chẳng có ích gì nếu bạn không thể tương tác với nó. Trong bài học tiếp theo, bạn sẽ học cách định nghĩa các phương thức bên trong hàm khởi tạo để đọc và chỉnh sửa những biến riêng tư này, giúp bạn truy cập có kiểm soát mà vẫn duy trì tính đóng gói thực sự.

challenge icon

Thử thách

Dễ

Hãy tạo một lớp SecretKeeper để minh họa tính riêng tư thực sự của dữ liệu bằng cách dùng closure! Khác với quy ước dùng dấu gạch dưới trong bài học trước, cách này khiến dữ liệu thực sự không thể truy cập từ bên ngoài đối tượng.

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

  • SecretKeeper.lua: Tạo một lớp lưu bí mật trong biến cục bộ bên trong hàm khởi tạo, không lưu trên self. Hàm khởi tạo :new(secretValue) cần lưu bí mật trong một biến cục bộ chỉ tồn tại bên trong closure. Hiện tại, đối tượng sẽ không có cách nào truy cập bí mật này (bài học tiếp theo sẽ hướng dẫn điều đó!), nhưng bạn cũng cần lưu một thuộc tính name công khai có giá trị "Keeper" trên instance để chúng ta có thể xác nhận đối tượng hoạt động.
  • main.lua: Nạp module SecretKeeper và đọc một giá trị bí mật từ đầu vào. Tạo một instance SecretKeeper với bí mật đó. Sau đó, chứng minh bí mật thực sự riêng tư bằng cách in ra ba thứ:
    • Thuộc tính name của keeper (kết quả cần in ra là Keeper)
    • Điều xảy ra khi bạn thử truy cập keeper.secret (kết quả cần là nil)
    • Điều xảy ra khi bạn thử truy cập keeper._secret (kết quả cũng cần là nil)

Bạn sẽ nhận một đầu vào:

  1. Giá trị bí mật cần lưu (ví dụ: TopSecret123)

Đầu ra cần có ba dòng, cho thấy thuộc tính công khai hoạt động còn bí mật thì hoàn toàn bị ẩn:

Name: Keeper
secret: nil
_secret: nil

Đầu ra sẽ giống nhau bất kể bạn truyền bí mật nào, vì không có cách nào truy cập nó! Biến cục bộ bên trong hàm khởi tạo vô hình với thế giới bên ngoài. Điều này chứng minh tính riêng tư dựa trên closure khác biệt căn bản với quy ước đặt tên bằng dấu gạch dưới.

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

Name: Keeper
secret: nil
_secret: nil

Tự làm thử

-- Nạp module SecretKeeper bằng require
local SecretKeeper = require('SecretKeeper')

-- Đọc giá trị bí mật từ đầu vào
local secretValue = io.read()

-- TODO: Tạo instance SecretKeeper với giá trị bí mật

-- TODO: In ra trường name của keeper (định dạng: "Name: <name>")

-- TODO: In ra kết quả khi truy cập keeper.secret (định dạng: "secret: <value>")

-- TODO: In ra kết quả khi truy cập keeper._secret (định dạng: "_secret: <value>")
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