Defensive Object Pattern in Lua

August 29, 2024

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