Refactor OOP implementation: redefine Vault and SubVault classes, enhance method visibility and initialization
This commit is contained in:
parent
1f595e867a
commit
c7237452df
125
main.lua
125
main.lua
@ -1,97 +1,48 @@
|
|||||||
oop = require("oop/oop")
|
oop = require("oop/oop")
|
||||||
|
|
||||||
function Animal(cls)
|
function Vault(cls)
|
||||||
function cls.__init(this, name)
|
cls.setattr(cls, "public_note", nil, "public")
|
||||||
this.name = name
|
cls.setattr(cls, "protected_note", nil, "protected")
|
||||||
end
|
cls.setattr(cls, "private_note", nil, "private")
|
||||||
|
|
||||||
function cls.speak(this)
|
cls.method("public_method", function(this)
|
||||||
print(this.name .. " makes a noise.")
|
print("public_method:", this.public_note)
|
||||||
end
|
end, "public")
|
||||||
|
|
||||||
function cls.info(this)
|
cls.method("protected_method", function(this)
|
||||||
print("Animal: " .. this.name)
|
print("protected_method:", this.protected_note)
|
||||||
end
|
end, "protected")
|
||||||
end
|
|
||||||
|
|
||||||
function Dog(cls)
|
cls.method("private_method", function(this)
|
||||||
cls.inherit(Animal)
|
print("private_method:", this.private_note)
|
||||||
|
end, "private")
|
||||||
function cls.speak(this)
|
|
||||||
cls.super.speak(this)
|
|
||||||
print(this.name .. " barks!")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Cat(cls)
|
|
||||||
cls.inherit(Animal)
|
|
||||||
|
|
||||||
function cls.speak(this)
|
|
||||||
print(this.name .. " meows.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Bird(cls)
|
|
||||||
cls.inherit(Animal)
|
|
||||||
|
|
||||||
function cls.speak(this)
|
|
||||||
print(this.name .. " chirps.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function CLI(cls)
|
|
||||||
cls.setattr(cls, "classes", {
|
|
||||||
Dog = Dog,
|
|
||||||
Cat = Cat,
|
|
||||||
Bird = Bird
|
|
||||||
})
|
|
||||||
|
|
||||||
function cls.__init(this)
|
function cls.__init(this)
|
||||||
this.classes = cls.classes
|
this.public_note = "hello"
|
||||||
end
|
this.protected_note = "shielded"
|
||||||
|
this.private_note = "secret"
|
||||||
function cls.prompt(this, label)
|
|
||||||
io.write(label)
|
|
||||||
return io.read()
|
|
||||||
end
|
|
||||||
|
|
||||||
function cls.handleActions(this, obj)
|
|
||||||
print("Type an action: speak, info (or 'back' to choose another animal).")
|
|
||||||
while true do
|
|
||||||
local action = this.prompt("action> ")
|
|
||||||
if not action or action == "back" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local method = obj[action]
|
|
||||||
if type(method) == "function" then
|
|
||||||
method()
|
|
||||||
else
|
|
||||||
print("Unknown action.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function cls.handleAnimal(this, choice)
|
|
||||||
local cls_ref = this.classes[choice]
|
|
||||||
if not cls_ref then
|
|
||||||
print("Unknown animal.")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local obj = oop.new(cls_ref, choice)
|
|
||||||
this.handleActions(obj)
|
|
||||||
end
|
|
||||||
|
|
||||||
function cls.run(this)
|
|
||||||
print("Type a class name: Dog, Cat, Bird (or 'quit' to exit).")
|
|
||||||
while true do
|
|
||||||
local choice = this.prompt("animal> ")
|
|
||||||
if not choice or choice == "quit" then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
this.handleAnimal(choice)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local cli = oop.new(CLI)
|
function SubVault(cls)
|
||||||
cli.run()
|
cls.inherit(Vault)
|
||||||
|
|
||||||
|
function cls.show_all(this)
|
||||||
|
print("inside SubVault:")
|
||||||
|
this.public_method()
|
||||||
|
this.protected_method()
|
||||||
|
this.private_method()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local v = oop.new(Vault)
|
||||||
|
print("outside Vault:")
|
||||||
|
print("public_note:", v.public_note)
|
||||||
|
v.public_method()
|
||||||
|
print("protected_note:", v.protected_note)
|
||||||
|
print("private_note:", v.private_note)
|
||||||
|
v.protected_method()
|
||||||
|
v.private_method()
|
||||||
|
|
||||||
|
local s = oop.new(SubVault)
|
||||||
|
s.show_all()
|
||||||
|
|||||||
163
oop/oop.lua
163
oop/oop.lua
@ -1,5 +1,14 @@
|
|||||||
local oop = {}
|
local oop = {}
|
||||||
|
|
||||||
|
local tpack = table.pack or function(...)
|
||||||
|
return { n = select("#", ...), ... }
|
||||||
|
end
|
||||||
|
local tunpack = table.unpack or unpack
|
||||||
|
|
||||||
|
local lookup_in_class
|
||||||
|
local lookup_with_owner
|
||||||
|
local lookup_visibility
|
||||||
|
|
||||||
local function super_call(self, method_name, ...)
|
local function super_call(self, method_name, ...)
|
||||||
local class = getmetatable(self)
|
local class = getmetatable(self)
|
||||||
if not class then
|
if not class then
|
||||||
@ -17,9 +26,59 @@ local function super_call(self, method_name, ...)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function_def_cache = setmetatable({}, { __mode = "k" })
|
local function_def_cache = setmetatable({}, { __mode = "k" })
|
||||||
|
oop._call_stack = {}
|
||||||
|
|
||||||
function oop.setattr(target, key, value)
|
local function normalize_visibility(visibility)
|
||||||
|
if visibility == nil then
|
||||||
|
return "public"
|
||||||
|
end
|
||||||
|
if visibility == "public" or visibility == "protected" or visibility == "private" then
|
||||||
|
return visibility
|
||||||
|
end
|
||||||
|
error("visibility must be 'public', 'protected', or 'private'")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_class_table(target)
|
||||||
|
return type(target) == "table" and rawget(target, "__visibility") ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function call_with_context(owner, fn, self, ...)
|
||||||
|
local stack = oop._call_stack
|
||||||
|
stack[#stack + 1] = owner
|
||||||
|
local results = tpack(pcall(fn, self, ...))
|
||||||
|
stack[#stack] = nil
|
||||||
|
if not results[1] then
|
||||||
|
error(results[2], 0)
|
||||||
|
end
|
||||||
|
return tunpack(results, 2, results.n)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function current_caller()
|
||||||
|
return oop._call_stack[#oop._call_stack]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function can_access(owner, visibility, caller)
|
||||||
|
if visibility == "public" then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if not caller or not owner then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if visibility == "private" then
|
||||||
|
return caller == owner
|
||||||
|
end
|
||||||
|
if visibility == "protected" then
|
||||||
|
return oop.issubclass(caller, owner)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function oop.setattr(target, key, value, visibility)
|
||||||
target[key] = value
|
target[key] = value
|
||||||
|
if is_class_table(target) then
|
||||||
|
local vis = normalize_visibility(visibility)
|
||||||
|
target.__visibility[key] = vis
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function oop.new(class, ...)
|
function oop.new(class, ...)
|
||||||
@ -32,13 +91,15 @@ function oop.new(class, ...)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
local obj = setmetatable({}, actual_class)
|
local obj = setmetatable({}, actual_class)
|
||||||
if actual_class.__init then
|
rawset(obj, "__fields", {})
|
||||||
actual_class.__init(obj, ...)
|
local init_fn, init_owner = lookup_with_owner(actual_class, "__init")
|
||||||
|
if init_fn then
|
||||||
|
call_with_context(init_owner, init_fn, obj, ...)
|
||||||
end
|
end
|
||||||
return obj
|
return obj
|
||||||
end
|
end
|
||||||
|
|
||||||
local function lookup_in_class(cls, key)
|
function lookup_in_class(cls, key)
|
||||||
local cur = cls
|
local cur = cls
|
||||||
while cur do
|
while cur do
|
||||||
local val = rawget(cur, key)
|
local val = rawget(cur, key)
|
||||||
@ -50,25 +111,99 @@ local function lookup_in_class(cls, key)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function lookup_with_owner(cls, key)
|
||||||
|
local cur = cls
|
||||||
|
while cur do
|
||||||
|
local val = rawget(cur, key)
|
||||||
|
if val ~= nil then
|
||||||
|
return val, cur
|
||||||
|
end
|
||||||
|
cur = rawget(cur, "__base")
|
||||||
|
end
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function lookup_visibility(cls, key)
|
||||||
|
local cur = cls
|
||||||
|
while cur do
|
||||||
|
local vis_table = rawget(cur, "__visibility")
|
||||||
|
if vis_table then
|
||||||
|
local vis = vis_table[key]
|
||||||
|
if vis then
|
||||||
|
return vis, cur
|
||||||
|
end
|
||||||
|
end
|
||||||
|
cur = rawget(cur, "__base")
|
||||||
|
end
|
||||||
|
return "public", nil
|
||||||
|
end
|
||||||
|
|
||||||
function oop.class(def, base)
|
function oop.class(def, base)
|
||||||
local cls = {}
|
local cls = {}
|
||||||
cls.__name = "Anonymous"
|
cls.__name = "Anonymous"
|
||||||
cls.__base = base
|
cls.__base = base
|
||||||
|
cls.__visibility = {}
|
||||||
cls.__index = function(self, key)
|
cls.__index = function(self, key)
|
||||||
local val = lookup_in_class(cls, key)
|
local fields = rawget(self, "__fields")
|
||||||
|
if fields then
|
||||||
|
local field_val = fields[key]
|
||||||
|
if field_val ~= nil then
|
||||||
|
local vis, owner = lookup_visibility(cls, key)
|
||||||
|
if not can_access(owner, vis, current_caller()) then
|
||||||
|
error("attempt to access " .. vis .. " member '" .. tostring(key) .. "'")
|
||||||
|
end
|
||||||
|
return field_val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local val, owner = lookup_with_owner(cls, key)
|
||||||
|
if val == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local vis, vis_owner = lookup_visibility(cls, key)
|
||||||
|
local access_owner = vis_owner or owner
|
||||||
|
if not can_access(access_owner, vis, current_caller()) then
|
||||||
|
error("attempt to access " .. vis .. " member '" .. tostring(key) .. "'")
|
||||||
|
end
|
||||||
if type(val) == "function" then
|
if type(val) == "function" then
|
||||||
return function(...)
|
return function(...)
|
||||||
return val(self, ...)
|
return call_with_context(owner, val, self, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return val
|
return val
|
||||||
end
|
end
|
||||||
|
cls.__newindex = function(self, key, value)
|
||||||
|
if key == "__fields" then
|
||||||
|
rawset(self, key, value)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local vis, owner = lookup_visibility(cls, key)
|
||||||
|
if owner and not can_access(owner, vis, current_caller()) then
|
||||||
|
error("attempt to set " .. vis .. " member '" .. tostring(key) .. "'")
|
||||||
|
end
|
||||||
|
local fields = rawget(self, "__fields")
|
||||||
|
if not fields then
|
||||||
|
fields = {}
|
||||||
|
rawset(self, "__fields", fields)
|
||||||
|
end
|
||||||
|
fields[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
function cls:super(method_name, ...)
|
function cls:super(method_name, ...)
|
||||||
return super_call(self, method_name, ...)
|
return super_call(self, method_name, ...)
|
||||||
end
|
end
|
||||||
function cls.setattr(target, key, value)
|
function cls.setattr(target, key, value, visibility)
|
||||||
return oop.setattr(target, key, value)
|
return oop.setattr(target, key, value, visibility)
|
||||||
|
end
|
||||||
|
function cls.method(name, fn, visibility)
|
||||||
|
if type(name) ~= "string" then
|
||||||
|
error("method name must be a string")
|
||||||
|
end
|
||||||
|
if type(fn) ~= "function" then
|
||||||
|
error("method must be a function")
|
||||||
|
end
|
||||||
|
cls[name] = fn
|
||||||
|
cls.__visibility[name] = normalize_visibility(visibility)
|
||||||
end
|
end
|
||||||
function cls.inherit(parent)
|
function cls.inherit(parent)
|
||||||
local actual_parent = parent
|
local actual_parent = parent
|
||||||
@ -80,7 +215,17 @@ function oop.class(def, base)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
cls.__base = actual_parent
|
cls.__base = actual_parent
|
||||||
cls.super = actual_parent
|
cls.super = setmetatable({ __class = actual_parent }, {
|
||||||
|
__index = function(_, key)
|
||||||
|
local val = actual_parent[key]
|
||||||
|
if type(val) == "function" then
|
||||||
|
return function(this, ...)
|
||||||
|
return call_with_context(actual_parent, val, this, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return val
|
||||||
|
end
|
||||||
|
})
|
||||||
local mt = getmetatable(cls) or {}
|
local mt = getmetatable(cls) or {}
|
||||||
mt.__index = actual_parent
|
mt.__index = actual_parent
|
||||||
mt.__call = mt.__call or function(c, ...)
|
mt.__call = mt.__call or function(c, ...)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user