What do you want to achieve? Keep it simple and clear!
I am writing a module/class script and I would like to disable the ability for users to directly set the value of properties. Without any other variable scopes besides global or local I am unable to do this. Is there another way of forcing users to use setter/getter methods instead of directly accessing the properties?
What is the issue? Include screenshots / videos if possible!
In some cases it is fitting or necessary to run data checks or run extra code following the setting of a property value. When users can bypass setter/getter methods they also bypass this extra code.
What solutions have you tried so far?
I have searched around a bit but haven’t found anything talking about this. Wondering if anyone else came across this issue and has a workaround.
Example Module code:
local myModule = {}
myModule.__index = myModule
myModule.ClassName = "Test Module"
myModule.Version = "1.0"
myModule.Author = "Emskipo"
function myModule.new()
local new = {}
setmetatable(new, myModule)
new.HardProperty = "I am a hard coded property!"
return new
end
function myModule:setNewProperty(value)
if(typeof(value) == "string")then
self.SoftProperty = value
else
error("New Property value must be a string")
end
end
function myModule:printProperties()
print(self.HardProperty)
if(self.SoftProperty)then
print(self.SoftProperty)
end
end
return myModule
Example Script Code:
local myModule = require(game:GetService("ServerScriptService"):WaitForChild("MyModule"))
newModule = myModule.new()
newModule:printProperties()
newModule:setNewProperty("I am a new dynamic property!")
newModule:printProperties()
newModule.SoftProperty = 451 --I have now bypass the requirement that this value be a string
newModule:printProperties()
Example Output:
I am a hard coded property! (x2)
I am a new dynamic property!
I am a hard coded property!
451
Keep a seperate table for the properties and use the __index and __newindex metamethods to reas and write to your property table, while handling private property checks inside of your __newindex handler. I am currently on mobile, so I cannot write a code sample.
This seems like it will be the solution to this issue. But I need a better understanding of these methamethods before I can implement it. Ty. If you could post an example, I would def. appreciate that.
I sort of did already. When you want to enforce a property value to be a certain data type such a string vs a number. But another situation might be when you want to say set a listener for a event such as a touch event on a part that a property value will hold. If you just set the part using the property itself then the listening event never gets implemented. I’m doing something similar for my case and its easier to enforce the user to use a function to set the value than it is to implement a complex mechanism to check if there is a value set or not and if the touch event was created. You can also enforce patterns in the data such as an array of a certain length or verify that other data has been set before hand that you might need to work with the property currently being set.
It is kind of like making sure you have exactly the right ingredients before you start cooking and that each of those ingredients was properly prepared before you use them. Otherwise you either use the ingredients as they were given and get totally unexpected results if they weren’t right or you check every ingredient as you go and if something isn’t right, you do a bunch of workarounds to fix the problem. Its more efficient and produces better results when you make sure your ingredients are perfect before you start.
So as was mentioned this is going to require some jigging of the __index and __newindex meta-methods. I’d also personally recommend the rare usage of weak tables for memory management, but they’re not necessarily needed.
So let’s get explaining:
The __index meta method follows the following flow chart:
The __newindex meta method can also be shown as a flow chart:
So keeping that in mind we can use these two meta methods to work with our class to get what you’re looking for.
-- This is for example a class module with its own scope
local MyClass = {}
local MyClass_mt = {}
local MyClass_Getters = {}
local MyClass_ReadOnly = setmetatable({}, {__mode = "k"})
-- Meta methods
function MyClass_mt.__index(self, key)
if (MyClass[key]) then
return MyClass[key]
elseif (MyClass_ReadOnly[self][key]) then
return MyClass_ReadOnly[self][key]
elseif (MyClass_Getters[key]) then
return MyClass_Getters[key](self)
else
error(key .. " is not a valid member of MyClass")
end
end
function MyClass_mt.__newindex(self, key, value)
if (MyClass_ReadOnly[self][key]) then
warn(key .. " is a read only property and cannot be changed")
end
end
-- Getters
function MyClass_Getters.SomeStringCount(self)
return #self.SomeString
end
-- Class Constructor
function MyClass.new()
local self = {
SomeNumber = 123,
SomeString = "ABC"
}
MyClass_ReadOnly[self] = {
ReadOnlyExample = "This cannot be changed externally"
}
return setmetatable(self, MyClass_mt)
end
-- Public Methods
function MyClass:MultiplyNum()
return self.SomeNumber * 2
end
function MyClass:ChangeReadOnly(newValue)
-- if you wanted to change the read only you could only do where MyClass_ReadOnly exists
MyClass_ReadOnly[self].ReadOnlyExample = newValue
end
--
return MyClass
And then you could use the class like so:
local test = MyClass.new()
-- methods and normal properties work like normal
test.SomeNumber = 5
print(test:MultiplyNum())
-- getter functions return based on the class
print(test.SomeStringCount)
-- can't set read only properties directly
print(test.ReadOnlyExample)
test.ReadOnlyExample = "new value!"
-- can do it through the class tho
test:ChangeReadOnly("new value two electric boogaloo")
print(test.ReadOnlyExample)
The output is as follows:
10
3
This cannot be changed externally
20:10:48.345 - ReadOnlyExample is a read only property and cannot be changed
new value two electric boogaloo
Y’know I read this over again, I think it makes more sense to specifically have to define setter behaviour and default to read only.
Here’s my revised example:
local MyClass = {}
local Properties = setmetatable({}, {__mode = "k"})
local Getters = {}
local Setters = {}
-- Meta-methods
function MyClass:__index(key)
if (MyClass[key]) then
return MyClass[key]
elseif (Getters[key]) then
return Getters[key](self)
elseif (Properties[self][key]) then
return Properties[self][key]
else
error(key .. " is not a valid member of MyClass")
end
end
function MyClass:__newindex(key, value)
if (Setters[key]) then
Properties[self][key] = Setters[key](self, value)
elseif (Properties[self][key]) then
warn(key .. " is a read-only property of MyClass")
end
end
function MyClass:__tostring()
return table.concat({self.x, self.y, self.z}, ", ")
end
MyClass.__metatable = false
-- Constructors
function MyClass.new(x, y, z)
local self = {}
x, y, z = x or 0, y or 0, z or 0
Properties[self] = {
x = x,
y = y,
z = z,
Magnitude = math.sqrt(x*x + y*y + z*z),
testProp = "abc"
}
return setmetatable(self, MyClass)
end
-- Getters
function Getters.Unit(self)
local props = Properties[self]
local x, y, z = props.x, props.y, props.z
local magnitude = props.Magnitude
if (magnitude > 0) then
return MyClass.new(x / magnitude, y / magnitude, z / magnitude)
else
return MyClass.new(0, 0, 0)
end
end
-- Setters
function Setters.testProp(self, value)
-- specifically have to define as not read-only
return value
end
-- Public Methods
function MyClass:Dot(self2)
return self.x*self2.x + self.y*self2.y + self.z*self2.z
end
--
return MyClass
I’m trying to implement this for functions specifically. Here is an example of what I am talking about in something like java:
public class thing{
public thing(){
}
public void DoThingPublicly(){ //Can be called at any time because it is a public method
}
private void DoThingPrivately(){ //Can ONLY be called in this script or others that share the scope
}
}
I have no clue how to implement this in roblox OOP. I found a hacky way of doing it by passing the object into the function in the module script but thats extremely tacky and I’m looking for a solution that could be more flexible. Here’s an example:
local module = {}
module.__ = module
local function thing(Obj)
print("YEAAAA!!")
end
function module.new(character)
local new = {}
setmetatable(new,module)
new.Character = character
return new
end
function module:DoIt()
thing(self)
end
return module
Can this be done or is this the best way to do it?