Technically yeah. This basically just creates a table class on top of the Instance API, and your custom class is no different than the standard OOP approach.
I originally designed this because I wanted to replace the attributes system to my own system that supports objects and tables. I haven’t gotten around doing so but you can replace existing methods and (and even properties) and have intellisense point to that one instead.
Example:
Read Usage & Example
If you want to create a psuedo Player Instance from scratch without relying on Instances, then it’s better if you just make your own class.
In terms of tests, I’ve tested properties, methods, events, and even direct children referencing (which amazingly intellisense still supports), but if you encounter any issues with anything else, I would be more than happy to find solutions for them.
I updated the code to export the type class for you to use!
Edit: I initially did that at the start but for some reason it was giving me DataModel before instead of instance type. I probably typed something wrong the first time around but it now works as expected.
local Class = require("./class")
local Component = {}
function Component.Get<T, K>(self: Class.Class<T, K>)
return getmetatable(self).__instance
end
function Component.SetProperty<T, K, U>(self: Class.Class<T, K>, property: string, value: U)
local instance = self:Get()
local _, success = pcall(function()
return (instance :: any)[property] and not (instance :: any):FindFirstChild(property)
end)
if success then
(instance :: any)[property] = value
end
end
I essentially have to redeclare self to have proper type checking. I also do function Class.method(self, ...) but it’s just syntatic sugar in the end.
function Class:Method(): ()
local self: Class<Instance, typeof(Class)> = self
-- though LSPs/linters wont like shadowing variables,
-- so either rename the variable or do function Class.Method(self)
end
This is really cool. Ive been wanting something like this for a while, but never knew how to do it. I didnt even know types allowed this.
Although, would this cause memory issues if used (for instance) for every player in a 30 player server?
Also, is there any reason not to have the type set like this:
local PlayerClass = {
Loaded = false,
}
type PlayerClass = Player & typeof(PlayerClass)
As opposed to what you suggested:
local ServerStorage = game:GetService("ServerStorage")
local Class = require(ServerStorage.Class)
local PlayerClass = {
Loaded = false,
}
type Class<instance, class> = Class.Class<instance, class>
type PlayerClass = Class<Player, typeof(PlayerClass)>
I didnt notice a difference, but Im asking just in case
Well as long as you remove any references it should be properly garbage collected. Otherwise, feel free to let me know or modify if you need to. This is nothing but a table class in the end.
And yeah, you can do that (I posted an example under the method NearFullIntellisense) Read Usage & Example
but it’s more for those who also wants intellisense for getmetatable(class) or those who needs the absolute type class.
Edit: Correct me if I’m wrong, but I believe you have to order it as typeof(PlayerClass) & Player as opposed to Player & typeof(PlayerClass) or else your override methods won’t show on autofill.
My only concern is that if I want to use a class with properties for multiple instances, I have to clone the entire class, which is a bit unusual compared to more traditional OOP modules (which usually have a constructor function). Of course you would have to set the properties anyway, but the functions being cloned is slightly worrying. I dont think it would matter enough to be an issue, but I dunno
That is totally fair, and it shouldn’t really be an issue. When you decide to use table.clone() or a custom clone, all you are doing to non-table values is simply this:
Read FAQ.
If you have a function, you’re not actually cloning that function, but simply pointing to that function reference. It is essentially the same behavior as your standard OOP (by creating a new table), but the difference is that there is no metatable attached to that class.
Read FAQ.
Additionally, the reason why you cannot do something like Class(player, PlayerClass.new()) is because of the fact that Class attaches a metatable to your class, so it will just completely override your other constructed metatable (or will error if you have a __metatable index).
Read FAQ.
Edit: Thank you for the questions by the way, I will probably update the original post to address all these concerns and to format it better
I updated the code to allow for new properties instead of throwing an error. You will have to set the allowNewProperties argument to true to bypass the legacy behavior.
I only made this module with attaching one class in mind, you’re going to have to rewrite one yourself. It’s probably possible by completely rewriting the metatable functions and by removing the Classes[instance] condition, but I say probably because I don’t know if Intellisense will like that.
Maybe I’ll end up writing a multi-class version soon, no guarantees though.