设计模式:C ++抽象层

时间:2016-08-01 12:31:37

标签: c++ c abstraction hal

我正在尝试编写一个抽象层,让我的代码在不同的平台上运行。让我举两个我最终想在高级代码中使用的类的例子:

class Thread
{
public:
    Thread();
    virtual ~Thread();

    void start();
    void stop();

    virtual void callback() = 0;
};

class Display 
{
public:
    static void drawText(const char* text);
};

我的麻烦是:我可以使用哪种设计模式让低级代码填写实现? 以下是我的观点以及为什么我认为它们不是一个好的解决方案:

  1. 从理论上讲,将上述定义放在highLevel/thread.h并且平台特定实现位于lowLevel/platformA/thread.cpp中是没有问题的。这是一个低开销的解决方案,可在链接时解决。唯一的问题是低级实现无法向其添加任何成员变量或成员函数。这使得某些事情无法实施。

  2. 一种解决方法是将其添加到定义中(基本上是Pimpl-Idiom):

    class Thread 
    { 
        // ...
    private:
        void* impl_data;
    }
    

    现在,低级代码可以拥有自己的结构或存储在void指针中的对象。这里的麻烦在于它难以阅读并且难以编程。

  3. 我可以使class Thread纯虚拟,并通过继承它来实现低级功能。高级代码可以通过调用这样的工厂函数来访问低级实现:

    // thread.h, below the pure virtual class definition 
    extern "C" void* makeNewThread();
    
    // in lowlevel/platformA/thread.h 
    class ThreadImpl: public Thread
    { ... };
    
    // in lowLevel/platformA/thread.cpp
    extern "C" void* makeNewThread() { return new ThreadImpl(); }
    

    这将足够整洁,但静态类失败。我的抽象层将用于硬件和IO事物,我真的希望能够拥有Display::drawText(...)而不是携带指向单个Display类的指针。

  4. 另一种选择是仅使用可在链接时解析的C样式函数,如extern "C" handle_t createThread()。这对于访问只有一次的低级硬件(如显示器)来说非常简单。但是对于任何可能存在多次的事情(锁,线程,内存管理),我必须在我的高级代码中携带句柄,这些代码很难看,或者有一个隐藏句柄的高级包装类。无论哪种方式,我都需要将句柄与高级别和低级别的相应功能相关联。

  5. 我最后的想法是混合结构。纯C风格的extern "C"函数用于低级别的东西,只有一次。工厂功能(见3.)可以存在多次。但是我担心混合会导致代码不一致,不可读。

  6. 我非常感谢能够设计出符合我要求的模式。

3 个答案:

答案 0 :(得分:1)

您不需要拥有与平台无关的基类,因为您的代码一次只针对单个具体平台进行编译。

只需将include路径设置为-Iinclude/generic -Iinclude/platform,并在每个受支持的平台的include目录中都有一个单独的Thread类。

您可以(并且应该)编写与平台无关的测试,编译和编译。默认情况下执行,它确认您的不同平台特定实现遵循相同的接口和语义。

PS。正如StoryTeller所说,Thread是一个不好的例子,因为它已经是一个可移植的std::thread。我假设您确实需要抽象一些其他特定于平台的细节。

PPS。你仍然需要弄清楚通用(平台无关)代码和特定于平台的代码之间的正确分割:在决定什么在哪里时,没有神奇的内容,只是在一系列权衡之间进行权衡重用/复制,简单与高参数化代码等。

答案 1 :(得分:0)

您似乎想要Thread类的值语义,并想知道在哪里添加间接使其可移植。所以你使用了pimpl成语和一些条件编译 根据您希望构建工具的复杂程度而定,并且如果您希望尽可能保留所有低级代码,请执行以下操作:

在高级标题Thread.hpp中,您可以定义:

class Thread
{
  class Impl:
  Impl *pimpl; // or better yet, some smart pointer
public:
  Thread ();
  ~Thread();
  // Other stuff;
};

在您的线程源目录中,您可以按照这种方式定义文件:

Thread_PlatformA.cpp

#ifdef PLATFORM_A

#include <Thread.hpp>

Thread::Thread()
{
  // Platform A specific code goes here, initialize the pimpl;
}

Thread::~Thread()
{
  // Platform A specific code goes here, release the pimpl;
}

#endif

构建Thread.o变得很简单,只需要获取Thread目录中的所有Thread_*.cpp文件,并让构建系统为编译器提供正确的-D选项。

答案 2 :(得分:0)

我很好奇,设计这种情况会如下所示(只是坚持线程):

// Your generic include level:
// thread.h
class Thread : public 
#ifdef PLATFORM_A
    PlatformAThread
#elif PLATFORM_B
    PlatformBThread
// any more stuff you need in here
#endif
{  
    Thread();
    virtual ~Thread();

    void start();
    void stop();

    virtual void callback() = 0;
} ;

不包含任何有关实现的内容,只包含接口

然后你有:

// platformA directory 
class PlatformAThread { ... };

这将自动导致您在创建“通用”Thread对象时自动获得一个平台相关类,该类自动设置其内部,并且可能具有特定于平台的操作,当然还有您的{{ 1}}类可能来自一个通用的PlatformAThread类,它具有您可能需要的常见内容。

您还需要设置构建系统以自动识别特定于平台的目录。

另外,请注意,我倾向于创建类继承的层次结构,有些人建议不要这样做:https://en.wikipedia.org/wiki/Composition_over_inheritance