• Advertisement
Sign in to follow this  

Encapsulating Software Modules/Components

This topic is 458 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello : )

I learned a lot about event-driven handling of my engine. I wanted to emphasise on encapsulation and making my components be able to not know about other components (bundles of logical sections in my code, graphics wrapper, scripting language wrapper, input recogniser) at all.

Example, why would a level-sprite want to know about the rendering window instance or what graphics library I'm using, when I can simply wrap around them.

Calling draw() should not be a task of the level, I assume.

But how do I encapsulate even more?

 

Nonetheless, I'm simply unable to absolutely encapsulate. I know, there has to be a bit of code shared among each other that can barely be encapsulated or rather isolated from other components.

But now, I would like to get some more input on how to keep my code clean and avoid this catastrophe:

 

I started to implement my event-listener-pattern to provide an easy communication between components.

One thing it does not seem to be made for is to communicate large bits of information, an example would be rendering sprites.

Calling the "draw-event" from within the level-class will create a small overhead for every element being rendered.

 

A bit more about my software-structure: Imagine that we start in the game-engine-class, somewhere everything has its origin.

The engine-class is handling game-states, not like a typical state-machine, as I wanted to have multiple states at once, too.

States can be: Main menu, level instance, level edit instance, pause menu, game over, ...

Additionally, I have a fair amount of game-components: Window, Input, Level, Event-Bus, Scripting-Language-Module, Texture-Store, ...

These are universally needed, so when I create my level, I have to pass a lot of references to the level-instance.

The level needs to know about the window, the level needs to know about the scripting-language-module and about the texture-store.

I considered moving the window inside a painter-class but not sure if that wouldn't just make my code complexer for actually no further advance (maybe clarity which is quite a powerful arguement).

Now, is there a way to stop this massive amount of passing references?

Especially since the chain of updating constructors gets more and more the further I progress with my game.

 

One solution: Bundling all modules into a module-store. Which literally means, it wraps around the modules. Upon construction of a state (or whatever is being composited within), they can require such a module.

Example: Level class requests the module-store for a window class and a scripting-language-wrapper, gets a reference, done.

Result would be avoiding to have one reference per module that is needed. Only one file is being passed around, the store.

 

What I dislike with this approach is its clarity. There might be classes that only require the scripting-language-wrapper, but they will get a reference to the whole store. Long-term wise this seems to be good, as expandability is increasing. Nonetheless, the ambiguous power of the store-class might be code-smell.

 

On the other hand, updating my initialiser lists is painful. Only because class A needs the graphics module, does not mean that class B within class A will require even more.

I'm theoretically planning my code on paper, that helps avoiding this, but sometimes things end up differently than I planned, heh.

 

But how are you solving this? How do professionals do?

 

 

 

 

Share this post


Link to post
Share on other sites
Advertisement

- You probably should just stop focusing on encapsulation for now and work with what you have. From what you're describing, you already have a plan for organizing components. Use it and keep it simple, and tweak it as per use case, like when you identify pieces of code that could fit into component models. Don't go in trying to encapsulate every single thing - this leads to a ton of wasted effort,code and debugging mindshare when you have to cross reference what the encapsulated data structures do. Write code that works for what you need, then decide if you need to encapsulate them(be reminded that this costs time).

 

- There are ways to stop passing a massive amount of references to subsystems per function call. I personally use a global struct of subsystems. Hence, i can do stuff like Systems::gMainRenderContext->somefunction.

Edited by ddengster

Share this post


Link to post
Share on other sites

Well, I would stop focusing on it if not my current way of handling components is coming with quite some backlash upon new implementations.

So I really need to stop this, as it is too time consuming too ignore it. I'm not sure if I got this right:

 

identify pieces of code that could fit into component models

But it seems not be really helpful when I'm talking about my modules. They are very unique and every class requires different modules.

Class B in class A might require more modules than A. Creating a larger bottleneck of passed references. Now, imagining class C in class B might require a complete different module, expanding the bottleneck among all classes in the  hierarchy even more.

 

Global objects are definitely the last thing I want, passing around needed references seems to be the better practice then. It clearly shows from where things originate.

Edited by Angelic Ice

Share this post


Link to post
Share on other sites

You're talking and overthinking very abstract, theoretical things, these discussions can get nowhere. You constantly state that you 'might need' things, so you try to cater for them by believing in some sort of catch-all solution. Do not get into this trap! This is in practice very bad - write code for what you really need over writing code for what you 'might need'. When you refactor code, aim to get rid of extraneous class definitions(a sign of over-engineering) or repeated code.

 

Also, I'm not sure which programming language you are using, but in c++ look up 'forward declarations', it's a way to have pointers to your classes without knowing the full definition of the class. And the classes you mentioned in your starting post like Level, scripting, states will touch a lot of the modules that are more isolated(input, graphics) - it's a fact, there's no need to make them more isolated/encapsulated.

Edited by ddengster

Share this post


Link to post
Share on other sites
It's hard to give any specific advice as we don't know where you're at exactly, but it sounds like a lot of your dependencies could well be inside out.
e.g. instead of calling Draw on a sprite, the drawable data can be pushed into a queue by the sprites, and a renderer/painter can later consume the queue. Side note: batch processing on large queues is superior to spaghetti flow control that jumps all over the place while doing many small bits of unrelated work.

Some of your dependencies may also be missing a third party. e.g. Having a level know about a window seems strange, but also a windows shouldn't know about a level. Sounds like there could be a level-painter that knows about levels and windows, while levels and windows don't know about each other nor the painter.

If you're using OO, OO design teaches dependency inversion, dependency injection and inversion of control as a solutions to these dependency chains. Often that means adding a "third" class between two previously dependent classes.

Also, dependencies should never be two way. Component A should always be aware of component B, while B has no knowledge of A's existence.

Not sure what your event bus is exactly, but you have to be carful getting addicted to using generic event passing systems as a decoupling technique. At first it will seem like you're decoupling components, but you're actually leaving the same dependencies in place while just obfuscating them and making your code less explicit...

Share this post


Link to post
Share on other sites

Hodgman posted in the meantime, hehe, I will read and edit this one accordingly.

Updated (below)!

 

My problem is about passing around a lot of module-dependencies into classes that do not require them but bloating initialiser lists of my constructors (yes, talking about C++).

And I have to change the amount of new references when a lower class requires a different module that the upper one did not require. It is bothersome, feels dirty and I was looking for alternatives compared to passing around sole modules.

 

Furthermore, my Level-class is not the same as my scripting-class.

My level-container-class is meant to be loaded and contain level-elements; there is the level-state class of course, that owns the a level-container. My scripting-class is a module that can be used by the level-class, it wraps around my scripting-language.

Input and graphics-class are modules that can be used as well and wrap around their respective idea.

So what I want is to learn about other ways to pass around references from my module-handler towards elements inside the level-container.

One road at the moment is:

Game-engine-class->Level-state->Level-container->Entity

 

 

 

write code for what you really need over writing code for what you 'might need'.

That is why I'm asking on how more experienced people are dealing with this issue in their projects or are actually avoiding this. E.g. you mentioned global objects, which avoids passing around modules at all.

Professional approaches probably make clear what modules will be needed and pass those exact ones.

Just wondering if they ever tend to bundle their modules together, so instead of passing references to each module, they pass a module-store, on which every class can require needed modules.

 

 

Edit for Hodgman's post:

 

 

Also, dependencies should never be two way. Component A should always be aware of component B, while B has no knowledge of A's existence.

This is what I'm doing.

 

 

 

Not sure what your event bus is exactly, but you have to be carful getting addicted to using generic event passing systems as a decoupling technique. At first it will seem like you're decoupling components, but you're actually leaving the same dependencies in place while just obfuscating them and making your code less explicit...

Yea, I already realised that it does not solve the decoupling issue.

You were talking about the painting-queue. I'm curious on how that would be put into a code-structure.

Let's assume we have my structure, which starts with a class that owns all modules (game-engine-class) (window wrapper, scripting language-wrapper, much more).

When the game-state says, let's iterate over all drawable objects inside the level-container (Game-engine-class->Level-state->Level-container->Entity), and I push the objects that shall be drawn into a queue, from where does the queue come and how does the renderer know about the queue?

Is the queue another module that resides inside the game-engine-class and is just brought to each class by reference?

I would pass it down to the level-state, putting needed drawings into the queue, then the engine calls the painter-class, uses the window-class, draws all needed entities.

 

Accordingly to Inversion of Control, I could use a locator, that would locate needed modules/services for my classes. Thanks a lot for these keywords, I will inform myself more about them : )

Edited by Angelic Ice

Share this post


Link to post
Share on other sites

My problem is about passing around a lot of module-dependencies into classes that do not require them but bloating initialiser lists of my constructors

 

Often, this is not a problem. Constructors are functions and giving them arguments is not an issue.

 

If you find yourself passing too many things into one constructor, it's possible that class is doing too much. Decompose it into multiple objects, and maybe pass those in instead.

 

I push the objects that shall be drawn into a queue, from where does the queue come and how does the renderer know about the queue?

 

The renderer owns the queue. Why make it more complicated than that?

Share this post


Link to post
Share on other sites

My problem is about passing around a lot of module-dependencies into classes that do not require them but bloating initialiser lists of my constructors

Often, this is not a problem. Constructors are functions and giving them arguments is not an issue.
 
If you find yourself passing too many things into one constructor, it's possible that class is doing too much. Decompose it into multiple objects, and maybe pass those in instead.

The other solution to this problem (although less frequently utilised in C++) are Factories.

If you are passing multiple arguments into one constructor, just so that it can forward them to the constructor of a child object... You may want to wrap the creation of the child object up into a Factory class, and just pass the factory instance around (having previously initialised it with the common parameters).

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement