Effective Way of Making a Method Inaccessible

Hey DevForum members,

I hope you’re keeping well.

In an attempt to make my Car class as versatile as possible, I want it to be able to adapt to missing properties.

I want to make property dependant methods inaccessible if we weren’t able to get the information we need upon construction.

The code below is purely to illustrate a use case. It works, but I know there’s a smarter solution out there.

Summary:

  1. Constructor called
  2. newcar:_propogateLightGroups() returns nil
  3. newcar.Lights = nil
  4. _failsafe() is moved inside the object and replaces setHeadlightState() as a direct reference
Car = {}
Car.__index = Car

-- Constructor. Returns a bus object.
function Car.new(carModel)
	local newcar = {}
	setmetatable(newcar, Car)

	newcar.Model 				= carModel
	-- Assume this returns nil.
	newcar.Lights 				= newcar:_propogateLightGroups()

	-- Let's make the method inaccessible if we don't have what we need.
	if not newcar.Lights then
		newcar.setHeadlightState = newcar._failsafe
	end
	
	return newcar
end

function Car:_failsafe()
	warn("The method was unable to execute due to missing properties.")
end

function Car:setHeadlightState(state)
	print("Headlights: ", state)
end

return Car

The Ideal Solution

Right now I’m unsure of how I could pass through additional values when remapping the method in my constructor. Being able to do this would be amazing.

function Car:_failsafe(func)
	warn(func or "undefined_function", " was unable to execute due to missing properties.")
end

What I’ve Considered

This too, would work. By adding a presence check in the method itself I could avoid things going wrong down the line. But it goes against the philosophy of writing repeatable code, and ideally I’d be able to avoid the method being called altogether.

function Car:setHeadlightState(state)
	if not self.Lights then self:_failsafe("setHeadlightState()") return end
	print("Headlights: ", state)
end

Thoughts, concerns and ideas are all appreciated. Thanks in advance! :blush:

1 Like

I don’t entirely know if I understand your issue; but you could do some sort of “hacky” method using __call and failsafe being a table.

newcar.setHeadlightState = setmetatable({
   -- Error information here; ie, name etc...
}, {
   __call = function(self, ...)
      newcar:_failsafe(self) -- Information here.
   end
});

Instead, you could write

newcar.setHeadlightState = function() newcar._failsafe("headlights") end

Is that what you were looking for?

1 Like

This is exactly what I was looking for, I knew I was missing something!

Simple & easy to read :+1:

1 Like

In that case something along these lines may be interesting to think about (not necessarily a good idea, but maybe an interesting read / thought experiment):

function Car:Disable(...)
	for i,functionName in ipairs(arg) do
		if not self[functionName] then warn("Warning, disabling unknown function: ", functionName) end
		self[functionName] = function() self._failsafe(functionName) end
	end
end

You could even factor it out to an external library, imported in your toplevel Car class. In any case, then in your constructor you can:

newCar:Disable("SetHeadlightState", "OtherFunctionName")

Mind, I did not test any of this, it’s just an idea, a bit hacky with function names as strings. But I think your concept is nice, for reasons like this.

It’s probably cleaner to keep the actual code free of messy links like this, but on the other hand, you won’t ever mention the wrong function name in your warning, and can wrap common safety checks (like that the disabled function exists) in an easily reusable function. Depends on context :smiley: But there is no way to overwrite a function variable without knowing the string that represents it, so I don’t see a ‘best of both worlds’.

1 Like