I've been an OO coder for years, and am slowly learning to break the habits of decades. I'd like to ask you folks about your favorite way of creating a standard hierarchy of this form:
- you have a 'Monster' class. It navigates and shoots. It knows which other monster is the 'Player' Monster.
- you have a 'Map' class. It can give you info on getting from a) to b), what is in each map cell etc.
Now the standard way I'd have done this in the past would be to have a World class containing both Monsters and a Map. Each instance of a Monster or the Map would be passed a 'World' so that when a Monster needs to go from a) to b) it can access the Map via the world.
(Alternatively, knowing there's only one Map, you could just make it (cough) a global. I know, I know. At least wrap them in a table to aid encapsulation.)
What's your favourite way of getting classes like these to communicate while obeying encapsulation... in lua-ey, non-OO way?
Avoiding OOP
Re: Avoiding OOP
Well, object oriented doesn't mean via classes, it just means you have an object that you can access properties on. Classes are just a way to build objects. Another way to build objects, is to just assign properties to an object you just came up with via lua's {} syntax.
As for how to structure things, something that's becoming rapidly common is using "Entity-Component Systems". The exact way to build them differs from person to person based on opinion and needs, but the general idea is the same. You throw all of your objects into one big bundle per gamestate (because the entities on your title screen wouldn't be the entities in your game world). Those objects are called "Entities" and they are themselves collections of Components. Then you write all of your logic in "Systems" which are provided an array full of entities and its up to each system to decide what to do with those objects.
A really good for instance would be a System in a Side-Scrolling-Platformer that applied gravity. When you called that system's update function, its input parameter is the list of entities. It goes through each entity and finds components that suggests that this entity is affected by gravity, it applies acceleration to the entity's Y component. Now you have a system that works for both the player, monsters, npcs, projectiles, etc.
One drawback to this system is that it lacks specialization, so having all of your particles managed by the entity component system would be pretty inefficient. You wouldn't make the game maps be an entity either, they'd be managed under a different scheme.
This system may feel a lot like it's a C style system, like how you might write code in a language that doesn't support classes, and you would be right to think so.
As for how to structure things, something that's becoming rapidly common is using "Entity-Component Systems". The exact way to build them differs from person to person based on opinion and needs, but the general idea is the same. You throw all of your objects into one big bundle per gamestate (because the entities on your title screen wouldn't be the entities in your game world). Those objects are called "Entities" and they are themselves collections of Components. Then you write all of your logic in "Systems" which are provided an array full of entities and its up to each system to decide what to do with those objects.
A really good for instance would be a System in a Side-Scrolling-Platformer that applied gravity. When you called that system's update function, its input parameter is the list of entities. It goes through each entity and finds components that suggests that this entity is affected by gravity, it applies acceleration to the entity's Y component. Now you have a system that works for both the player, monsters, npcs, projectiles, etc.
One drawback to this system is that it lacks specialization, so having all of your particles managed by the entity component system would be pretty inefficient. You wouldn't make the game maps be an entity either, they'd be managed under a different scheme.
This system may feel a lot like it's a C style system, like how you might write code in a language that doesn't support classes, and you would be right to think so.
Re: Avoiding OOP
Off the top of your head, how would you answer these questions?
- Does OOP necessitate the use of classes (or class-like things), or can OOP exist without classes?
- If you're using classes (or class-like things), does that necessarily mean you're using OOP?
- What are the advantages of OOP over procedural programming? Disadvantages?
- What does functional programming mean in a language like Lua?
- In a multi-paradigm language like Lua, how does using a particular paradigm differ from using a particular design pattern?
- Is it possible to write OOP code in a declarative way?
Re: Avoiding OOP
I would indeed have a map variable which is exposed to the current game state.
This variable would always point to the current map which is in a list of maps.
To load another map I would just change what map is referring to.
I think it's ok that the monsters assume that there is a map, because a monster doesn't make much sense outside of the context of a map, and I don't have to worry about breaking encapsulation because my game state is just an environment which I can throw away and reload.
Conceptually I think the big difference to your approach is that you need to expose your World to your Map/Monster by passing it, so that your Map/Monster can query information from it. I would rather see the world as a data structure which your map and monsters are part of.
To the outside this would just be a table, to your gameplay code it would be an environment.
If you haven't played around with environments yet, here's a quick rundown:
Environments are basically Lua tables that your functions use to look up global variables.
If you haven't set an environment your functions will look up values in the global environment, which can also be manually accessed as table _G.
To set an environment of a function in Lua 5.1 you use setfenv.
Here's a simple example:
So you can see that even though we say 'x = 2' in foo, this assignment is safely encapsulated into the table env.
One thing that is also useful to know is that in Lua, files can be seen as functions, so you can just load them as functions and set an environment to them as well!
Your function only has access to the contents of your environment, so you have to prefill it with the stuff you want to expose.
An __index metatable (e.g. _G) can be particularly handy here to only have read only access.
Environments can also be nested to restrict access even further.
This blog post also gives a small example of environment usage in part 3.
This variable would always point to the current map which is in a list of maps.
To load another map I would just change what map is referring to.
I think it's ok that the monsters assume that there is a map, because a monster doesn't make much sense outside of the context of a map, and I don't have to worry about breaking encapsulation because my game state is just an environment which I can throw away and reload.
Conceptually I think the big difference to your approach is that you need to expose your World to your Map/Monster by passing it, so that your Map/Monster can query information from it. I would rather see the world as a data structure which your map and monsters are part of.
To the outside this would just be a table, to your gameplay code it would be an environment.
If you haven't played around with environments yet, here's a quick rundown:
Environments are basically Lua tables that your functions use to look up global variables.
If you haven't set an environment your functions will look up values in the global environment, which can also be manually accessed as table _G.
To set an environment of a function in Lua 5.1 you use setfenv.
Here's a simple example:
Code: Select all
x = 1
env = {}
function foo()
x = 2
end
setfenv( foo, env )
foo()
print( x, env.x ) -- this outputs 1, 2
One thing that is also useful to know is that in Lua, files can be seen as functions, so you can just load them as functions and set an environment to them as well!
Code: Select all
local env = {}
f = love.filesystem.load"foo.lua"
setfenv( f, env )
f()
An __index metatable (e.g. _G) can be particularly handy here to only have read only access.
Environments can also be nested to restrict access even further.
This blog post also gives a small example of environment usage in part 3.
Re: Avoiding OOP
Good question. I assume that OOP involves structures holding both data & functions that act on that data, subclasses & encapsulation.airstruck wrote:Off the top of your head, how would you answer these questions?
Does OOP necessitate the use of classes (or class-like things), or can OOP exist without classes?
If you're encapsulating and subclassing, then yes. If you're not subclassing then the classes are really just containers of data+methods. Is this *strictly* OOP? Yes. Is this the 'full spirit' of OOP? Not really. It's just being tidy!If you're using classes (or class-like things), does that necessarily mean you're using OOP?
Advantages are (theoretically) reusable code (i.e. less repetition enabled by extension of base classes), clarity of what data your functions are designed to work on.What are the advantages of OOP over procedural programming? Disadvantages?
Considering the potential complexity of Lua's return types, FP should be fairly unrestrained. Apart from that, I haven't coded in FP since Miranda in the '90s! I'll need to do some research.What does functional programming mean in a language like Lua?
My understanding of design patterns is pretty sketchy. I know it's simply a formalized way of approaching common problems, but that also sounds like it would involve a choice of paradigm to me! I guess you're saying that problems are problems, and each can be solved in a number of ways. A specific OOP approach and a functional approach can co-exist in Lua.In a multi-paradigm language like Lua, how does using a particular paradigm differ from using a particular design pattern?
Nnnnnghhhh... my brain hurts. Again - not touched Prolog in decades either. Looking at 'declarative Lua' examples on the web, there are some wacky sections of code such as:Is it possible to write OOP code in a declarative way?
Code: Select all
function doThing(x,y)
return function(x,y)
function f(x, y)
-- Do stuff here
return(x1,y1)
end
return x,y
end
end
Indeed, but thanks for the suggestion that I think about these things. That's why I started this thread. I never even considered Lua as a functional/declarative language before.I'm not asking you to answer them all, just think about how you'd answer them. As other people reply to this thread, consider that they might have their own views on how these questions ought to be answered.
@undef Thanks for the prods on scoping, environment and namespaces. I'll be doing some tests on using these more effectively.
Re: Avoiding OOP
Here's my perspective on it. It's just my opinion, so take it with a grain of salt.
Yes, there is the added benefit that methods of a superclass are "very close by" in a subclass, but this is really just a convenience, and not always worth it.
The clarity thing is nice, but not a huge deal; good naming conventions could take care of that in procedural code.
The inheritance thing can also be nice, but it's a double-edged sword.
The only real disadvantage, I think, is that sometimes you just don't want the functions coupled to any data... except that you could just have a singleton class that manages data but doesn't hold any data itself, if you were bent on all-out OOP, so that's not really a huge deal.
Anyway, all that aside, I don't think you need to ditch OOP, but I think you may have put inheritance on somewhat of a pedestal. I'd try to look for other ways of reuse (composition), and think about the other things you like about OOP, and also think about the bad parts of inheritance. The ECS pattern discussed earlier is a good example of how code reuse can be achieved in a more flexible way without inheritance in some cases (of course like any pattern it's only useful in certain situations).
I agree with this, except for the "subclasses" part. I don't think subclassing (or even "classing") is critical for OOP. Of course, all that proves is that we have different ideas about what OOP means (or maybe different ideas about what features of OOP are the most important).Madrayken wrote:Good question. I assume that OOP involves structures holding both data & functions that act on that data, subclasses & encapsulation.
I see what you're getting at, but I think the benefits of subclassing are just a side effect of the fact that subclasses will share a common interface when treated as instances of a superclass. Having objects that share a common interface is what's important to me about OOP; this is where the polymorphism benefits come from. The interface here doesn't need to formally exist, it can just design by contract.If you're encapsulating and subclassing, then yes. If you're not subclassing then the classes are really just containers of data+methods. Is this *strictly* OOP? Yes. Is this the 'full spirit' of OOP? Not really. It's just being tidy!
Yes, there is the added benefit that methods of a superclass are "very close by" in a subclass, but this is really just a convenience, and not always worth it.
To me, the main advantage of OOP over procedural is polymorphism. In other words, It doesn't matter how an object does something, and we don't need to specify a particular implementation of a function when doing something with an object. We just say objectA:doSomething() and the object is responsible for knowing how to do it. This is why the concept of "structures holding both data & functions that act on that data" is important, in my opinion.Advantages are (theoretically) reusable code (i.e. less repetition enabled by extension of base classes), clarity of what data your functions are designed to work on.
The clarity thing is nice, but not a huge deal; good naming conventions could take care of that in procedural code.
The inheritance thing can also be nice, but it's a double-edged sword.
The only real disadvantage, I think, is that sometimes you just don't want the functions coupled to any data... except that you could just have a singleton class that manages data but doesn't hold any data itself, if you were bent on all-out OOP, so that's not really a huge deal.
To me, it means you have the option of using patterns like mapreduce, memoize, bind, and so on. It also means you can make use of first-class functions and closures to write functions with very few assignments and no side effects. I think this can co-exist perfectly happily with OOP code. For example, nothing would stop you from giving your own data structures "each" or "map" as methods.Considering the potential complexity of Lua's return types, FP should be fairly unrestrained. Apart from that, I haven't coded in FP since Miranda in the '90s! I'll need to do some research.
You got it, Toyota.My understanding of design patterns is pretty sketchy. I know it's simply a formalized way of approaching common problems, but that also sounds like it would involve a choice of paradigm to me! I guess you're saying that problems are problems, and each can be solved in a number of ways. A specific OOP approach and a functional approach can co-exist in Lua.
Yes, it's possible! Take a look at the excellent d3.js, my favorite example of an object-based API with a very declarative feel.Nnnnnghhhh...
Anyway, all that aside, I don't think you need to ditch OOP, but I think you may have put inheritance on somewhat of a pedestal. I'd try to look for other ways of reuse (composition), and think about the other things you like about OOP, and also think about the bad parts of inheritance. The ECS pattern discussed earlier is a good example of how code reuse can be achieved in a more flexible way without inheritance in some cases (of course like any pattern it's only useful in certain situations).
Re: Avoiding OOP
I don't really understand. Lua allows you to implement OO in your programming, if you want to use it. Is there a reason you want to avoid it?
Re: Avoiding OOP
I'm not asking about a perceived shortcoming of Lua, but about ways to break certain OO habits given Lua's flexibility. The answer? "Don't be so dogmatic".
Re: Avoiding OOP
I don't think you need anything. Adhering to some arbitrary programming patterns just kills creativity and damages productivity. If the management crew was the internet, programming patterns would be ebin memes. Write more to do less, perfectly sane concept, amarite guize?!!
In my practice, none of that self-restriction ever helped. Like there I want to do something that would be performant, simple and fool-proof, but nooo, some jackass says this breaks a paradigm. So what if it does? Why would you care? The computer wouldn't care, that's for certain. End user would be the last person on the planet to care about proper use of patterns, but you can bet your ass he'd be the first to care if it runs poorly, and no amount of "code elegance" will redeem the situation.
When I code, I just concentrate on getting it to work properly, and then on getting it to work faster. That's all the end user would care for, and so it's all what I care for.
Keep in mind that overengineering is a bad thing. In physical hardware it just leads to expensive items with poor reliability outside of test conditions that are so complicated it's impossible to fix them. In programming it has similar effect.
Also, I don't know why would anyone suggest that "properly written" code is reusable, as opposed to other code like it isn't. Just a reminder, using libraries = code reuse. Do you care what's in the library? Pretty sure not. Chances are you never looked into sources.
In my practice, none of that self-restriction ever helped. Like there I want to do something that would be performant, simple and fool-proof, but nooo, some jackass says this breaks a paradigm. So what if it does? Why would you care? The computer wouldn't care, that's for certain. End user would be the last person on the planet to care about proper use of patterns, but you can bet your ass he'd be the first to care if it runs poorly, and no amount of "code elegance" will redeem the situation.
When I code, I just concentrate on getting it to work properly, and then on getting it to work faster. That's all the end user would care for, and so it's all what I care for.
Keep in mind that overengineering is a bad thing. In physical hardware it just leads to expensive items with poor reliability outside of test conditions that are so complicated it's impossible to fix them. In programming it has similar effect.
Also, I don't know why would anyone suggest that "properly written" code is reusable, as opposed to other code like it isn't. Just a reminder, using libraries = code reuse. Do you care what's in the library? Pretty sure not. Chances are you never looked into sources.
- zorg
- Party member
- Posts: 3465
- Joined: Thu Dec 13, 2012 2:55 pm
- Location: Absurdistan, Hungary
- Contact:
Re: Avoiding OOP
I'd agree with most, except for the fact that by "properly written code is reusable", they probably mean either of two things:
- the source code is readable in that it's not a nightmare to actually figure out what and how it does things
- the interfacing part(s) are not combining very differing schemes like getElement, but set_elem with elementreset etc...
But readable code can become just as much of a flustercluck as a bowl of sphagetti-code, it mostly depends on the coder. ;3
Also, design patterns aren't that generic/general to begin with; specific languages "need" specific patterns because by default they cannot do a thing well without them. By well i mean either cleanly or fast or with the least memory usage. For example, design patterns that mimic first order functions are probably moot with lua, since there, you can treat functions as any other type.
- the source code is readable in that it's not a nightmare to actually figure out what and how it does things
- the interfacing part(s) are not combining very differing schemes like getElement, but set_elem with elementreset etc...
But readable code can become just as much of a flustercluck as a bowl of sphagetti-code, it mostly depends on the coder. ;3
Also, design patterns aren't that generic/general to begin with; specific languages "need" specific patterns because by default they cannot do a thing well without them. By well i mean either cleanly or fast or with the least memory usage. For example, design patterns that mimic first order functions are probably moot with lua, since there, you can treat functions as any other type.
Me and my stuff True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
Who is online
Users browsing this forum: Bing [Bot] and 3 guests