Defensive Object Pattern
local function createDefensiveObject(initialData)
local data = initialData or {}
local methods = {}
local readOnlyKeys = {}
local mt = {
__index = function(t, k)
return methods[k] or data[k] or error("Missing key: "..k, 2)
end,
__newindex = function(t, k, v)
if readOnlyKeys[k] then error("Read-only: "..k, 2) end
if not data[k] then error("Use addProperty(): "..k, 2) end
if type(v) ~= type(data[k]) then error("Type mismatch: "..k, 2) end
data[k] = v
end,
__pairs = function() return next, data, nil end
}
local obj = {}
function methods.addProperty(key, value, readOnly)
if data[key] then error("Duplicate: "..key, 2) end
data[key] = value
if readOnly then readOnlyKeys[key] = true end
end
function methods.addMethod(name, fn)
if methods[name] then error("Duplicate: "..name, 2) end
methods[name] = fn
end
return setmetatable(obj, mt)
end
-- Usage
local person = createDefensiveObject{
name = "Alice",
age = 30
}
person:addProperty("id", "123", true)
person:addMethod("greet", function(self)
return "Hello, "..self.name
end)
-- Fails:
-- person.id = "456" (read-only)
-- person.email = "a@b.com" (new key)
-- person.age = "31" (type mismatch)
- Immutable by default
- Type checks on updates
- Methods/properties whitelist
- Read-only flags
- Error chain (2) shows caller line
Use for: configs, validated data, strict APIs