Getting attributes from a class (OOP)

What does the code do and what are you not satisfied with?
To keep the explanation simple I will use apples as an example.
So I have an apple class that is used to create apple objects for players to use. However, I also need to retreive certain attributes from the apple class that have constant values so apples can be displayed and baught in the games store.

Below is a simplified version of the apple class

-- Module script for the apple class
local Item = require(--path to the item class)

local apple = {}
apple.__index = apple
apple.ClassName = "apple"

setmetatable(apple,{
	__index = Item,
	__call = function(cls,...)
		local self = setmetatable({}, cls)
		self:init(...)
		
		return self
	end
})

--Const Attributes
local ConstAttributes = {}
ConstAttributes.cost = 20
ConstAttributes.icon = "rbxassetid://137510460"


--Constructor
function apple:init(Player,cost,img,cooldown,stack)	
	--Attributes
	self.n = Instance.new("IntValue")
	self.n.Value = stack --number of apples the player has left
	self.CCD = 0 --Current cooldown for the player
	self.TCD = cooldown --Total cooldown 0 seconds after use
	
	--Inheritance
	Item.init(self,Player,cost,img)
end

--Methods
function apple:Action()
	--Player eats an apple, heals, cooldown begins etc
end

--Function for retrieving class attributes
function apple.GetConstAttributes()
	return ConstAttributes
end

return apple

This works but I’m essentially using the same module script as both a class and object. I may be over thinking this but this doesn’t seem like good programming practice.

What potential improvements have you considered?
The only other alternative I can think of to get these values is to write a new “profile class” which will contain these values and have two methods. One for retreiving these values and another to call the apples constructor (which it will pass these values into). However this would mean writing a new profile object for each and every item I make and feels kind of redundant since it would be a class just for calling another class.

That’s how it should be done. Your ModuleScript acts as a class which then other scripts can require and create objects of this class.

As for the improvement request at hand, you are overthinking it a bit. Your constants are class-level variables so they should also be accessible through the class table (apple) rather than a separate one with a getter method. This will allow you to index class-level attributes from an object.

You already have this figured out with the ClassName, you just need to follow that for the rest of your attributes. ClassName is also a constant attribute of the class.

local apple = {}
apple.__index = apple

apple.ClassName = "apple"
apple.cost = 20
apple.icon = "rbxassetid://137510460"

Just on an unrelated note, I figure it might be good for you to keep some kind of consistency with your naming conventions, such as using PascalCase for the properties. As for the constructor, OOP would normally use an explicit function for that rather than the call metamethod - looks cleaner. Ultimately though, you do what feels most comfortable for you.

Example of how I write my classes:

local Item = require("Item")

local Apple = setmetatable({}, {__index = Item})
Apple.__index = Apple

Apple.ClassName = "Apple"
Apple.Cost = 20
Apple.Icon = "rbxassetid://137510460"

function Apple.new()
    local object = setmetatable({}, Apple)

    return object
end

function Apple:Method()
    print("Apple method")
end

return Apple
2 Likes