【问题标题】:C++ app: modules designC++ 应用程序:模块设计
【发布时间】:2011-01-07 05:32:45
【问题描述】:

我的应用程序包含几个模块(大类),如网络 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 风格。第三种方式保持指针有点混乱(尽管指针的有效性不会有问题,尽管模块一直存在,但仍然存在)并且有两阶段初始化,这不能保证我们不会忘记初始化一些模块,然后哎呀——一个无效的指针。如果某些对象需要交叉绑定,第二种方式(我认为)可能会使所有想法崩溃。第一种方式似乎很自然(因为模块代表应用程序本身),但它不是一种糟糕的风格吗?我看到了一些项目,其中模块是在某个 Universe 类中声明的,它们通过这个 Universe 进行合作,就像它们都是全局的一样。你怎么看?

谢谢。

【问题讨论】:

  • 一般来说,大 class== 糟糕的设计

标签: c++ module


【解决方案1】:

我会选择 #2,并分解任何循环依赖项。为什么 A 和 C 都需要相互了解?是否可以将依赖项分解为单独的组件?

【讨论】:

  • 确实:网络不应该知道控制或数据存储;这也会使测试单个模块变得非常困难
  • 现在就是这样。谢谢,所以我会坚持下去并尝试打破依赖关系。
【解决方案2】:

尽可能避免使用全局数据,因此最好的解决方案可能是 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);
   }
}

【讨论】:

    【解决方案3】:

    从计算机科学的角度来看,理想情况下,您希望尽可能减少耦合。从一般开发的角度来看,您希望减少在某处进行更改时编译的代码量。

    因此,为了解决这个问题,您需要使用接口和“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 ();
    }
    

    一旦代码被拆分,只改变接口会导致大量的重新编译,改变接口的实现只会重新编译改变的模块,不需要重新编译。

    【讨论】:

    • 很漂亮。尽管在项目的当前阶段,#2 变体已经足够了,但我会记住这个解决方案,以防项目增长。谢谢。
    • 查看代码,有一些地方可以改进。摆脱 void * 之一 - 替换为一个通用模块接口(即所有模块接口都继承自的东西),它可能有 GetModuleName 之类的东西。
    【解决方案4】:

    我建议您查看发现 here 的中介者设计模式。它允许您定义一种在类/模块之间进行通信的方式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-30
      • 1970-01-01
      • 1970-01-01
      • 2011-08-27
      • 2014-01-26
      相关资源
      最近更新 更多