Приложение C ++: дизайн модулей - PullRequest
1 голос
/ 20 января 2010

Мое приложение содержит несколько модулей (больших классов), таких как сеть io, хранилище данных, элементы управления и т. Д. Некоторые из них могут взаимодействовать. Что было бы хорошим способом объявить и связать модули? Я вижу несколько возможностей:

1) Все модули объявлены как глобальные, поэтому мы имеем в main.cpp

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

и если a хочет поговорить, например, с модулем c, в a.cpp будет следующее:

#include "c.hpp"
extern ModuleC c;

и т. Д .;

2) Все модули объявлены в main (), поэтому они являются локальными. Привязки производятся в конструкторах:

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

но при этом было бы сложно связать объекты, которые хотят друг друга (a (c), c (a))

3) Первый этап: объявить локально, второй этап: связать с указателями:

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

Есть ли лучший способ? Я бы хотел, чтобы это было в стиле cpp. Третий способ удерживает указатели, что немного сбивает с толку (хотя с валидностью указателей проблем не возникнет, хотя модули все время живут, но все же) и имеет двухфазную инициализацию, которая не гарантирует, что мы не забудем init некоторый модуль, а ой - неверный указатель. Второй способ (как я думаю) может разрушить всю идею, если некоторые объекты нуждаются в перекрестном связывании. Первый способ кажется естественным (поскольку модули представляют само приложение), но разве это не плохой стиль? Я видел несколько проектов, в которых модули были объявлены в каком-то классе юниверсов, и они взаимодействуют через этот юниверс, как это было бы, если бы все они были глобальными. Что ты думаешь?

Спасибо.

Ответы [ 4 ]

3 голосов
/ 20 января 2010

Я бы пошел с # 2 и разбил все циклические зависимости. Почему A и C оба должны знать друг о друге? Может ли зависимость быть выделена в отдельный компонент?

0 голосов
/ 20 января 2010

Я бы посоветовал вам взглянуть на шаблон проектирования посредника, найденный здесь . Позволяет определить способ связи между классами / модулями.

0 голосов
/ 20 января 2010

С точки зрения информатики, в идеале вы хотите максимально уменьшить связь. С общей точки зрения разработки вы хотите уменьшить объем кода, который компилируется, когда вы вносите изменения где-либо.

Итак, чтобы решить эту проблему, вы захотите использовать интерфейсы и класс 'Universe'.

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;
};

Обратите внимание, что это означает, что конструкторы модулей не могут вызывать функции в других модулях, поскольку порядок построения фактически не определен.

Чтобы использовать вышеизложенное, ModuleA может иметь такую ​​функцию:

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

Итак, собрав все вместе, вот пример программы, которая компилируется и запускается с использованием DevStudio 2005 (создайте пустое консольное приложение по умолчанию):

#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 ();
}

Как только код разделен, только изменения в интерфейсах вызовут большую перекомпиляцию, изменение реализации интерфейсов приведет только к перекомпиляции измененного модуля, больше ничего не нужно будет перекомпилировать.

0 голосов
/ 20 января 2010

Насколько вы можете, всегда избегайте глобальных данных, поэтому лучшим решением, вероятно, является смесь из 2) и 3):

Поскольку объекты должны иметь ссылку на другой объект (и в противном случае они недействительны), такой объект должен быть передан конструктору. Тем самым ваша версия 2) лучше. В тех случаях, когда это невозможно (так как все объекты ссылаются друг на друга), было бы неплохо создать еще один составной класс ModuleABC, который заботится о построении и ссылках на все объекты. Чтобы избежать экземпляров модулей, которые не имеют всех необходимых ссылок, их конструкторы лучше всего закрывать, а ModuleABC следует объявить как друга:

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);
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...