[OOP] how do I create a class that inherits from multiple classses?

How do I make a class that inherits from multiple classes?
Making a class inherit from a single classs is easy, (just extend the table) but how do I make it inherit from multiple classes?

This is the currernt setup I have.
The NPC class I have currently uses the FSM ( finite state machine ) and State ( which is wirtten in the same module as the fsm class but independent from the fsm ) class.
The current setup I have simply stores the fsm instance as a variable in the npc class
The problem I have is that the fsm instance doesnt have a direct access to the variables of the npc class. ( the comments are the examples )
The only way I could think of is passing the npc object as a aruguemt the same way “self” works. Im not sure if this is the right way, it feels messy and prone to bugs…

How do I use multiple classes in a class?
Thanks for taking your time

Edit:
The example only inherits from one class currently, however I will be adding more classes to it.
This might be a bad example but I need help coming up with the basic strucure that allows multiple classs inheritance. thanks again

local MyClass = game:GetService("ServerScriptService").MyClass
local FSM = require(MyClass.FSM)
local State = FSM.State

local NPC = {}
NPC.__index = NPC
NPC.fsm = nil

NPC.pursuitTarget = nil

function NPC.new()
	local new = {}
	setmetatable(new, NPC)
	new.fsm = FSM.new({
		pursuit = State.new({
			enter = function()
				--I want to check if NPC.pursuitTarget is not nil here
				
				print("Enterd pusuit state")
			end,
			update = function()
				--walk to target
			end,
		})
	})
	return new
end

return NPC
2 Likes

Do not inherit from multiple classes.

Currently your setup which is this:

Is actually a HasA relationship which is composition relationship, the NPC has a finite state machine which is good

Check out @BRicey763, Composition concept over inheritance video.

Inheritance in lua would look more like this:

local Fruit = require(path)

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

function Apple.new()
  local newApple = Fruit.new("Apple") -- This object
  return setmetatable(newApple, Apple)
end
--Compared with yours
	local new = {}
	setmetatable(new, NPC)

So yeah there should be nothing wrong with passing down the properties of NPC down towards the FSM constructor.

Personally, I’m still learning OOP and all I know is that inheritance creates messy code where you have to look back and forth between the two module scripts where a simple table of properties only is enough.

5 Likes

I never actually worked in a language that supports multiple inheritance (not counting Lua, supporting :sparkles: everything :sparkles: ), so I have no idea if that’s even a good idea. In this case at least, I’m fairly sure it doesn’t make sense, just like @dthecoolest suggested.

I’ll try to explain why, and what you should do instead.

You’ve created a FSM class that, presumably, should provide an abstraction to the rest of your code allowing it to use FSMs without thinking about how to implement it. If that’s the case, it must be fairly generic, to support usage in many different situations. Being generic means that it can’t depend on implementation details in the situations where it’s used, because those details will be different in other situations. That means your FSM objects can’t know about NPCs, so choosing an architecture that does that (which is what you’re asking for) goes against your decision to create an FSM class in the first place, and nullifies all the advantages that come with creating the class. The idea also fails the “is-a/has-a” test, an NPC is not a state machine, it just needs to a have one.

In short, your FSM class should not depend on your NPC class.

Luckily, it doesn’t need to know about the NPC class to affect it. You’ve created your FSM class to work with callbacks *which are defined by the user of the FSM class. Those callbacks can simply access the specific NPC instance through upvalues to the callback functions that you pass to FSM.new. You can literally just do

new.fsm = FSM.new({
		pursuit = State.new({
			enter = function()
                --Accessing `new` as an upvalue to this function
                print("Entering pursuit state")
				print(("The pursuitTarget is %s"):format(tostring(new.pursuitTarget)))
			end,
			update = function()
				--walk to target
			end,
		})

This works and is still a good separation of concerns between the NPC and FSM classes. The job of an FSM is not to know what happens when specific states are entered, it’s just to know about the different states and which state transitions are valid. Knowing what it means to “enter the pursuit state” is the job of the NPC, because that’s not related at all to FSMs and is completely related to pursuing things, which is what an NPC does. And in the code you’ve provided, it’s the NPC class that defines the callbacks and thus what it means to enter the pursuit state.

There are other ways you could connect the FSM logic and the logic of being in the different specific states, like event based programming (there are some great “signal” libraries out there, highly recommended).

It’s not clear from your FSM class though if it deals at all with the question of “what state to transition to”. One way to do that is to return the name of the next state from the “update” callback, and transition to whichever state has that name every time state.update runs.

Hope this helps and makes sense :sweat_smile: Take what I say with a grain of salt because again, I’ve never worked with multiple inheritance and I’m not an expert on any of this, it’s just what I’ve picked up over the years.

2 Likes