In my project, I have multiple subsystems organized as classes.
I need those classes to communicate (so be able to access the other one via a pointer), and I want to implement this in the best way as possible.
I basically see three possible solutions here:
If subsystem X needs to access subsystem Y, add a member variable to class X pointing to an instance of Y. When X is created, pass to it the pointer to Y and have the member variable m_pSystemY
set.
Declare a global variable CSystemX * g_SystemX
for every subsystem. It will be filled with a pointer to the freshly created subsystem instance on program start. Later on, you can easily access it from anywhere.
Create a sophisticated subsystem manager class. All subsystems are stored in an array. You need to call a function in order to access a specific subsystem.
My questions:
Exposing entire class via a pointer to other classes will create a tight coupling throughout the system, and thus breaks the "law of demeter". You could probably make it better by known design patterns, e.g. mediator pattern. just a thought...
How funny, I see one answer in each direction!
Anyway, what I recommend you to do it following option #1 (add a member variable to class X pointing to an instance of Y) with a small correction: use an interface instead.
So, each module will have an interface. This interface will have the methods that people from outside might need to do their job. Everything else is hidden from them.
When module A is created, if it needs to use features of module B, you'll do something like setModuleBHandler(moduleB)
.
This way, you can change the modules that do the things you need without the caller noticing it (because the interface is still honored).
This will allow you to follow other good practices such as TDD.
Example:
Suppose module Broker needs logging (and you have a module dedicated to logging). Instead of declaring public void setLogger(Logger logger)
, prefer to declare it as follows: public void setLogger(LoggerInterface logger)
.
This is almost the same. What worked before will still work. The difference is that now you can use some completely different Logger implementation without having to worry about impacts in the myriad of modules that can be using them.
For instance, you can have a class TextLogger implementing the interface and another DBLogger implementing the same interface. You can interchange them without expecting any problems.
Note: using an abstract class will get you nearly the same results.
I am aware that this small example doesn't properly show the full advantages of this choice and many people will be able to find drawbacks in it, but I strongly believe that this is the best choice for most cases (of course each case is a case).
I wouldn't use global variables at all. I would pass pointers to other systems via an initialization function. Whoever is instantiating these system and initializing them can decide how to store them, be that their own global variables, static class members, singleton wrappers, or (most appropriately) a member of some unifying class (IE: class CGame). Either way, passing them in at initialization is the most modular, readible, and maintainable without sacrificing ease of use in any way.
I would go with the subsystem manager class. I would implement this using templating and make sure that the subsystems implement the same interface (that is c# lingo not sure what the c++ equivalent is) so they can all talk to each other.
If you classes are always just going to have one instance, then you can treat them as singletons:
class singleton
{
// Private constructor
singleton() {}
// Static instance defaul to null in cpp
static singleton *instance;
public:
// Access function (put in cpp)
static singleton* getInstance()
{
if(!instance) instance = new singleton();
return instance;
}
};