tags:

views:

98

answers:

4

Hi

My app contains several modules (big classes) like network io, data storage, controls, etc. Some of them can cooperate. What would be a good way of declaring and binding the modules? I see several possibilities:

1) All modules declared as global, so we have in main.cpp

#include ...
ModuleA a;
ModuleB b;
ModuleC c;

and if a wants to talk to module c for example, we will have in a.cpp the following:

#include "c.hpp"
extern ModuleC c;

and so on;

2) All modules declared in main(), so they are local. The bindings are made in constructors:

int main() {
 ModuleC c;
 ModuleA a(c);
 ModuleB b;
}

but in this way would be hard to bind objects which want each other ( a(c), c(a) )

3) First phase: declare locally, second phase: bind with pointers:

int main() {
 ModuleA a;
 ModuleB b;
 ModuleC c;
 a.Connect(&b);
 b.Connect(&a);
 c.Connect(&a, &b);
}

Is there a better way? I'd like it to be in cpp style. Third way keeps pointers which is a bit confusing (though there won't be problems with validness of pointers though modules lives all the time, but still) and has two-phase initialization, which doesn't guarantee that we won't forget to init some module, and oops -- an invalid pointer. The second way (as i think) may crash all the idea if some objects would need cross-binding. The first way seems to be natural (since modules represent the app itself), but isn't it a bad style? I saw some projects where the modules were declared in some universe class and they cooperate via this universe just like it woule be if all of them are global. What do you think?

Thanks.

+3  A: 

I'd go with #2, and break up any circular dependencies. Why do A and C both need to know about each others? Can the dependency be factored out into a separate component?

jalf
indeed: network should not know about control or data storage; that would also make it very hard to test a single module
stijn
This is how it is done now. Thanks, so i'll stick to it and will try to break up dependencies.
vedro so snegom
A: 

As far as you can, always avoid global data, so the best solution is probbly a mixture from 2) and 3):

As far as Objects must have a link to another object (and are otherwise invalid) such a object should be passed with the constructor. Thereby your version 2) is better. In cases where that is not possible (as all objects refer to each other) it might be a good idea to create another composite class ModuleABC that takes care of construction and references of all objects. To avoid instances of Modules that dont have all needed links, their constructors should best be private, and ModuleABC should be declared as friend:

class ModuleABC:

class ModuleA
{
private:
     ModuleA();
     friend ModuleABC
...
};
class ModuleB
{
private:
     ModuleB();
     friend ModuleABC
...
}
class ModuleC
{
private:
     ModuleC();
     friend ModuleABC
...
}

class ModuleABC
{
public:
   ModuleA a;
   ModuleB b;
   ModuleC c;

   ModuleABC() 
   {
      a.Connect(&b, &c);
      b.Connect(&a, &c);
      c.Connect(&a, &b);
   }
}
RED SOFT ADAIR
A: 

From a computer science point of view, you ideally want to decrease the coupling as much as possible. From a general development point of view, you want to reduce the amount of code that is compiled when you make a change somewhere.

So, to address this, you'll want to use interfaces and the 'universe' class.

main ()
{
   Universe my_app;
   ModuleA a (my_app);
   ModuleB b (my_app);
   ModuleC c (my_app);
}

class ModuleA : public ModuleAInterface
{
public:
  ModuleA (Universe &my_app) : m_my_app (my_app)
  {
    m_my_app.Register (this);
  }
private:
  Universe &m_my_app;
}

// etc...

class Universe
{
public:
  template <class T>
  void Register (T *module)
  {
    m_modules [T::ModuleID] = module;
  }

  template <class T>
  T *Module ()
  {
    return reinterpret_cast <T *> (m_modules [T::ModuleID]);
  }
private:
  std::map <int, void *> m_modules;
};

Note that this means that Module constructors can't call functions in other modules as the order of construction is effectively undefined.

To use the above, ModuleA might have a function like:

void ModuleA::SomeFunction ()
{
  ModuleBInterface *b = m_my_app.Module <ModuleBInterface> ();
}

So, putting it all together, here's a sample program that compiles and runs using DevStudio 2005 (create a default, empty console application):

#include <map>
#include <iostream>

class Universe
{
public:
  template <class T>
  void Register (T *module)
  {
    m_modules [T::ModuleID] = module;
  }

  template <class T>
  T *Module ()
  {
    return reinterpret_cast <T *> (m_modules [T::ModuleID]);
  }
private:
  std::map <int, void *> m_modules;
};

class ModuleAInterface 
{
public:
  static const unsigned ModuleID = 1;
  virtual ~ModuleAInterface () {};
};

class ModuleBInterface 
{
public:
  static const unsigned ModuleID = 2;
  virtual ~ModuleBInterface () {};
  virtual void OutputString (char *string) = 0;
};

class ModuleCInterface 
{
public:
  static const unsigned ModuleID = 3;
  virtual ~ModuleCInterface () {};
};

class ModuleA : public ModuleAInterface
{
public:
  ModuleA (Universe &my_app) : m_my_app (my_app)
  {
    m_my_app.Register (this);
  }
  void DoSomething ()
  {
    ModuleBInterface *b = m_my_app.Module <ModuleBInterface> ();
    b->OutputString ("Hello");
  }
private:
  Universe &m_my_app;
};

class ModuleB : public ModuleBInterface
{
public:
  ModuleB (Universe &my_app) : m_my_app (my_app)
  {
    m_my_app.Register (this);
  }
private:
  virtual void OutputString (char *string) { std::cout << string; }
  Universe &m_my_app;
};

class ModuleC : public ModuleCInterface
{
public:
  ModuleC (Universe &my_app) : m_my_app (my_app)
  {
    m_my_app.Register (this);
  }
private:
  Universe &m_my_app;
};

int main ()
{
  Universe my_app;
  ModuleA a (my_app);
  ModuleB b (my_app);
  ModuleC c (my_app);
  a.DoSomething ();
}

Once the code has been split up, only changes to the interfaces will cause large recompilation, changing the implementation of the interfaces will only recompile the changed module, nothing else will need to be recompiled.

Skizz
It is beautiful. Though on the current stage of project, there would be enough the #2 variant, i'll keep in mind this solution in case project will grow. Thank you.
vedro so snegom
Looking over the code, there are a few bits that could be improved. Getting rid of the `void *` for one - replacing with a common module interface (i.e. something all module interfaces inherit from) which could have stuff like `GetModuleName` and so on.
Skizz
A: 

I would advice you to look at the mediator design pattern found here. It allows you to define a way to communicate between classes/modules.

Tomas