Efficient Object Oriented Programming Tutorial

I thought we were talking about metamethods. Anyways, subclasses inherit from (optionally) one base class. it’s a one way street. It does not inherit anything else, other than setting its metatable to Rcade.

Also Rcade is a module script. that’s just where the metamethods are located and where you create classes and use Utility. Which you said you like using module scripts for DRY. Why not Rcade?

If you need to inherit you do it inside the class as well in my examples. You have to write the inheritance in with __super. It’s not like they magically inherit something else out of nowhere. And they only inherit from one class, this system does not support multiple inheritance (meaning 1 class inheriting from 2 different classes)

Also, yes an extra layer makes it less readable. While it was a point I made in the tutorial, it’s something I sacrificed (a little) to have a better user experience. Though if you follow the tutorial you should very well understand what the code is trying to do. Some things need a learning curve like anything.

I assume that the issues you have pointed out are

  • the extra layer reduces readability
  • the metamethods inside Rcade have an effect on every class created through Rcade which is bad?

However, this has had noticeable results in my development. And I’ve seen other OOP concepts outside of LUA that use a third layer. Not saying that would make this system inherently good. However, I don’t agree with your statement. Imagine the third layer like the class implementations that are built in into other coding languages natively. That’s what I was going for anyway.

If you didn’t want metamethods inherited from one place, then sure, don’t add the extra layer. Though subclasses would not inherit metamethods base classes.

The one thing I never figured out how to do was how to have more than one constructor depending on the types of parameters and the amount of parameters. If you had an example of this it would be great since I could try to implement it into the system.

I do like getting your feedback.

Thanks!

04/21/2023: This is bad advice so hiding it from immediate view. Please check out the post above.

It depends on what you mean by metamethods? When talking OOP using the term metamethods is confusing for anything other than explaining how class-level items for OOP-in-Lua is implemented. I’m specifically talking about classes and inheritance and why another layer is unnecessary.

Rcade might be intended to be a ModuleScript but that doesn’t automatically mean it fits within my vision of DRY/SRP application with modules. I’ve mentioned in my posts already that I prefer to be explicit about the way I write my code and follow common OOP patterns which include forthright declarations of my constructors and inheritance in code.

Turning a constructor into a function with the __call metamethod is a pointless abstraction of a standard new constructor and makes it less obvious what you’re doing in code. Additionally, if your class supports multiple constructors, then you have inconsistency in your code which is bad for readability.

-- With Rcade
local Thing = MyClass(args)
local Thing2 = MyClass.fromSomething(something)

-- Without Rcade
local Thing = MyClass.new(args)
local Thing2 = MyClass.fromSomething(something)

Just so I can clear the air here: is your module intended to be some kind of implementation of a MiddleClass? I know that some developers do in fact follow this pattern but I don’t see the large benefit unless you explicitly provide utility with the module that makes it worth using. This is what I initially see your module as trying to accomplish and that’s nothing like the native implementations other languages have. They too also have explicit declarations, i.e. in Java you extend a class.

For constructors, you simply need to declare them in your class if you want more than one; and if you want one constructor to be overridden based on the amount of arguments given (which you really should not do anyway), you can either swap arguments around via if statements or use varargs to count or pack arguments into something workable. If applicable, new constructors can also just call the main constructor with some arguments autofilled:

local Space2D = {}
Space2D.__index = Space2D

function Space2D.new(xScale, xOffset, yScale, yOffset)
    local object2D = setmetatable({}, Space2D)

    object2D.X = {Scale = xScale, Offset = xOffset}
    object2D.Y = {Scale = yScale, Offset = yOffset}

    return object2D
end

function Space2D.fromScale(xScale, yScale)
    return Space2D.new(xScale, 0, yScale, 0)
end
3 Likes

Yes a middleclass is exactly what I was going for. Never heard the term before, thanks for bringing it to my attention.


I agree that it does make it less obvious. However my thinking was that it would keep class objects from being able to call the new function from the metatables. However I realize you can keep it from being indexed by objects by editing the __index metamethod. Is it wrong to think that objects shouldn’t be able to call constructors themselves? Or is there a benefit in doing so? is it pointless to worry?

I see I have overcomplicated the idea in my head. I appreciate the example.

You have great points. I’ll be working on a way to make it more explicit, and give opportunity to multiple constructors. I’m glad I posted this here since I wouldn’t have been corrected if I hadn’t!

The point is you are meant to abstract this away, yes someone might be able to do cls.new().new().new().new() forever, but they shouldn’t be programming like that anyway, Same for cls.new().__index.__index.__index, but again, why would someone do this.

3 Likes

It seems like the topic was bumped (due to editing, I didn’t remember posting this anyway?)

I know how to simplify this now.

local function GetDataStore(name: string)
    return setmetatable({
        _dataStore = DataStoreService:GetDataStore(name);
        _dataStoreName = name;
    }, {__index = dataStoreMethods})
end

Better yet, use module:GetDataStore() and just redirect the other function to it (i have a __call thing?).

I stopped using if false then statements to help on prediction because I started using Rojo and that is basically a non issue. It actually does help Rojo from my last testing though. But it’s harder to read, but it’s still whatever. But it has not worked sometimes for me when I have systems that are too in deep. Like a class that gets another class inside another class, etc.


Overall, your explanation on everything is really good. I knew that already as of time of reading this, I haven’t gotten over everything, but I can pretty much see that you showed a lot of good points here.

Wow, been almost 2 years since I’ve read my own post on this thread, but since to this day I still notice it bleeds a few likes upon unsuspecting observers, I’d like to give a follow up to this now that I’ve had the chance to work on more serious projects and rack up experience.


Addressing my reply and therefore the original thread: I was wrong in calling the Rcade class layer unnecessary. Very very wrong, and I’ll deep dive into that.

I almost literally use the exact pattern shown for Rcade now when creating classes and it not only helps with making inheritance much easier and simpler but also for consistency among classes because it abstracts creating the class container boilerplate away. vocksel/class from Wally.

The way the class module works is it creates a basic class container (the whole shebang - the table, the ClassName, inheritance support for the class and its metamethods and so on) and you instead write everything that you want happening on construction in an init function. Not as extensive but it gets the necessities out of the way so you don’t repeat yourself constantly.

The rest of the information can be gleaned from the thread itself which describes the use of the pattern; although they are implemented differently, both the class library and Rcade from the thread both achieve the same intended end result of better class creation.

The posts I made in this thread prior were born out of a place of misplaced confidence in what I believed was correct without either actually having tried out the pattern or being familiar with it and knowing what its drawbacks may have been, if there were any. So over 2 years later, I want to clear the air and apologise for misleading information and terrible advice in my posts. There was a whole lot of words to say virtually nothing very useful and said from a place of inexperience.


Addressing my own points directly:

  • Rcade is unnecessary and ruins readability: Flat out false. Readability is not affected by the use of a middleclass or class creation abstraction. You are simply picking a different location to write your object’s properties for construction.

  • Constructors are made to be explicit: init is explicit. This point makes no sense.

  • Some classes may (require or) desire more than one constructor: Possible to do even with an abstracted class. It may look a bit messy though especially if your new is created somewhere else but alternate constructors are essentially supposed to just change how the base constructor is called as opposed to making a whole new unrelated object.

  • Inheritance is quicker and cleaner with two metatables: Not in most cases honestly. Certainly not quicker and debatable on if it’s cleaner. With the class library I’ve been using as linked earlier, inheritance is incredibly easy - I just have to think in terms of writing properties alone.

  • I wouldn’t need a whole layer: This is true. It’s also true that I like to be consistent among my projects. I don’t think there’s a good or bad answer here since different content has different parameters but this isn’t a view I hold any longer. Speedy provided a good follow up on why the layer is good which is addressing metatables. It is also correct that it’s very easy to break DRY.

  • “An object should only have upwards inheritance”: In my second post, all my comments about inheritance are bold ways to say that the only OOP I know is OOP-in-Roblox and know nothing about encapsulation, polymorphism or any tenets of true OOP. This is just a mess of a reply here, and I was also wrong in saying that OP doesn’t understand the application of DRY.


Although my posts already each have replies, I’m going to hide the content and replace them with a message pointing out this reply. I don’t want any more developers being led by bad advice I gave over 2 years ago when they find this thread because it’s not good for their learning.

This is a good thread with good explanations and a pattern worth using. And even if you don’t want to go to this extent, consider giving vocksel’s class a look as well (especially if you’re a Wally user!).

6 Likes

I find it crazy that it has already been 2 years. I must admit Rcade has its issues, with the biggest problem it has is being more complicated than it has to be.

Back then I don’t think I was knowledgeable enough to explain my intentions clearly. However, thanks to your previous posts colbert, I was driven to improve on it while maintaining the codes original intent.

Since then, I’ve made big improvements to the system which led to increased readability and efficiency. I’m thinking of posting the updated system after making a game based on it.

I appreciate the feedback even this long after! I assume after 2 years we’ve both learned a lot more than we did before, if I ever make any new posts, I hope to have your feedback again!

4 Likes

that would throttle because the object is already created

how DO I POST

never imagine a 100 yr old can shout

3 Likes

So why would we use all these self classes and metatables when you can just do this

local Players = game:GetService("Players")
local player = Players.LocalPlayer
print(player.Name, player.AccountAge)

then just setup a function to check if any message set is containing “AH” as the first to letters to detect if its “AH” or “AHHHHH” or “AHHHHHHHHHHHHHHHHH” and then just return the players name, and if we know the name, we know the account age.

That was just an example of how the OOP system works. You can make much more difficult programs that without the OOP system, would be a mess. OOP is really good at organizing your code into concepts (classes and objects).

Also, with the example I wasn’t necessary meaning the Account age, I meant real age. Of course, you would never use these examples in real Roblox games.

2 Likes

Do you have an updated version of it?

1 Like

Your Class constructor idea is great! when i first looked at it i didn’t understand it completely, but after analyse i understood it removes the same stuff from class constructors and turn it into one short function

Thanks to your tutorial i always use constructor and it’s very very easy to work with OOP now

Is there a way to get this to work with intellisense? Intellisense works up until we start getting into the advanced part of the tutorial where we enable the possibility of inheritance, then it just stops detecting anything :<

1 Like

Thank you for this resource, It was a very interesting read!

1 Like

The way I’ve got around the issue of intellisense, is to define object types within each class module script and exporting them so those types can be used outside of the module script. Types can use the concept of inheritance too! When I create an object, I specify that the variable is of the object type found in the class module script.

I’ve actual developed a pattern of how I develop classes using this system now. In short, I first define the type of the object (the properties and functions). Then I write the class and its logic following the type I created.

The only issue with this is if you don’t keep the type up to date with what’s actually in the class, the mismatch can be confusing and can lead to errors.

Yeahh I should’ve specified if it was possible to do typeof() on the class modules and not having to set up manual types, but it makes sense that we would have to manually create the types.

1 Like

inheritance is a generally bad way to structure your code IMO. I’d recommend to switch to something like an ECS. The type definitions can be pretty annoying to see and write and separating everything into components is preferable.

1 Like

I’m still an avid defender of the OOP paradigm. I know it seems to be pretty popular to hate on OOP nowadays, but there’s definitely still a reason for its existence, plus inheritance is very useful if used correctly, avoiding deep inheritance. OOP is great for organizing code, so I think it works great for large games.

The ECS pattern can get pretty complex when you have a large number of components that all interact with each other though systems. And lua devs tend to implement ECS in a way that removes all of the efficiency gains it’s known for.

In the end, code in whatever pattern / paradigm that makes the most sense for you. But don’t be afraid to learning new patterns / paradigms as well, you might find one that works better for you! My mind thinks in an OOP way, so I usually use OOP in my games.

I’m currently developing an OOP based Archetype ECS system in Lua, trying to tie in some of the benefits of ECS and OOP together. It’s a fun project!

1 Like

yeah I fully understand that, I still use OOP too, It’s very convenient for small independent objects, I just prefer ECS for games as a whole, you can also use composition in OOP instead of inheritance. As you said, to each their own.

2 Likes