设计模式:C++抽象层
Design pattern: C++ Abstraction Layer
我正在尝试编写一个抽象层,让我的代码 运行 在不同的平台上运行。让我举一个我最终想在高级代码中使用的两个 classes 的例子:
class Thread
{
public:
Thread();
virtual ~Thread();
void start();
void stop();
virtual void callback() = 0;
};
class Display
{
public:
static void drawText(const char* text);
};
我的烦恼是:我可以用什么设计模式让低级代码填充实现?
以下是我的想法以及为什么我认为它们不是一个好的解决方案:
理论上,将上述定义放在 highLevel/thread.h
中并将特定于平台的实现放在 lowLevel/platformA/thread.cpp
中是没有问题的。这是一个在 link 时间解决的低开销解决方案。唯一的问题是低级实现不能向它添加任何成员变量或成员函数。这使得某些事情无法实施。
一种出路是将其添加到定义中(基本上是 Pimpl-Idiom):
class Thread
{
// ...
private:
void* impl_data;
}
现在低级代码可以将自己的结构或对象存储在 void 指针中。这里的麻烦是它读起来很丑,编程很痛苦。
我可以使 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(); }
这已经足够整洁了,但对于静态 classes 却失败了。我的抽象层将用于硬件和 IO 事物,我真的希望能够拥有 Display::drawText(...)
而不是携带指向单个 Display
class.
的指针
另一种选择是仅使用可以在 link 时解决的 C 风格函数,例如 extern "C" handle_t createThread()
。这对于访问只存在一次的低级硬件(如显示器)来说非常简单。但是对于任何可以多次出现的东西(锁、线程、内存管理),我必须在我的高级代码中携带句柄,这很丑陋或者有一个隐藏句柄的高级包装器 class。无论哪种方式,我都有必须将句柄与高级和低级端的相应功能相关联的开销。
我最后的想法是混合结构。纯 C 风格 extern "C"
函数用于仅存在一次的低级内容。工厂函数(参见 3.),用于可以多次出现的东西。但我担心混合的东西会导致不一致、不可读的代码。
如果能提供符合我要求的设计模式提示,我将不胜感激。
您不需要平台不可知的基础 class,因为您的代码一次只针对一个具体平台编译。
只需将包含路径设置为 -Iinclude/generic -Iinclude/platform
,并在每个支持的 平台 的包含目录中有一个单独的线程 class。
您可以(并且应该)编写与平台无关的测试,默认情况下编译和执行,以确认您的不同平台特定实现遵循相同的接口和语义。
PS。正如 StoryTeller 所说,Thread 是一个糟糕的例子,因为已经有一个可移植的 std::thread
。我假设您确实需要提取一些其他特定于平台的细节。
PPS。您仍然需要弄清楚通用(与平台无关)代码和特定于平台的代码之间的正确划分:没有灵丹妙药可以决定什么去哪里,只是[之间的一系列权衡=26=]、简单代码与高度参数化代码等
您似乎想要 Thread
class 的值语义并且想知道在哪里添加间接以使其可移植。所以你使用 pimpl 惯用语,和一些条件编译。
根据您希望构建工具的复杂性所在的位置,并且如果您希望尽可能保持所有低级代码的独立性,您可以执行以下操作:
在你高级别headerThread.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
选项。
很好奇,如果把这种情况设计成下面这样(紧贴主题)会是怎样的:
// 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 { ... };
这将自动导致当您创建 "generic" Thread
对象时,您也会自动获得一个依赖于平台的 class,它会自动设置其内部结构,并且可能具有平台特定操作,当然还有您的 PlatformAThread
class 可能源自具有您可能需要的共同事物的通用 Base
class。
您还需要将构建系统设置为自动识别特定于平台的目录。
此外,请注意,我倾向于创建 class 继承的层次结构,有些人反对这样做:https://en.wikipedia.org/wiki/Composition_over_inheritance
我正在尝试编写一个抽象层,让我的代码 运行 在不同的平台上运行。让我举一个我最终想在高级代码中使用的两个 classes 的例子:
class Thread
{
public:
Thread();
virtual ~Thread();
void start();
void stop();
virtual void callback() = 0;
};
class Display
{
public:
static void drawText(const char* text);
};
我的烦恼是:我可以用什么设计模式让低级代码填充实现? 以下是我的想法以及为什么我认为它们不是一个好的解决方案:
理论上,将上述定义放在
highLevel/thread.h
中并将特定于平台的实现放在lowLevel/platformA/thread.cpp
中是没有问题的。这是一个在 link 时间解决的低开销解决方案。唯一的问题是低级实现不能向它添加任何成员变量或成员函数。这使得某些事情无法实施。一种出路是将其添加到定义中(基本上是 Pimpl-Idiom):
class Thread { // ... private: void* impl_data; }
现在低级代码可以将自己的结构或对象存储在 void 指针中。这里的麻烦是它读起来很丑,编程很痛苦。
我可以使
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(); }
这已经足够整洁了,但对于静态 classes 却失败了。我的抽象层将用于硬件和 IO 事物,我真的希望能够拥有
的指针Display::drawText(...)
而不是携带指向单个Display
class.另一种选择是仅使用可以在 link 时解决的 C 风格函数,例如
extern "C" handle_t createThread()
。这对于访问只存在一次的低级硬件(如显示器)来说非常简单。但是对于任何可以多次出现的东西(锁、线程、内存管理),我必须在我的高级代码中携带句柄,这很丑陋或者有一个隐藏句柄的高级包装器 class。无论哪种方式,我都有必须将句柄与高级和低级端的相应功能相关联的开销。我最后的想法是混合结构。纯 C 风格
extern "C"
函数用于仅存在一次的低级内容。工厂函数(参见 3.),用于可以多次出现的东西。但我担心混合的东西会导致不一致、不可读的代码。
如果能提供符合我要求的设计模式提示,我将不胜感激。
您不需要平台不可知的基础 class,因为您的代码一次只针对一个具体平台编译。
只需将包含路径设置为 -Iinclude/generic -Iinclude/platform
,并在每个支持的 平台 的包含目录中有一个单独的线程 class。
您可以(并且应该)编写与平台无关的测试,默认情况下编译和执行,以确认您的不同平台特定实现遵循相同的接口和语义。
PS。正如 StoryTeller 所说,Thread 是一个糟糕的例子,因为已经有一个可移植的 std::thread
。我假设您确实需要提取一些其他特定于平台的细节。
PPS。您仍然需要弄清楚通用(与平台无关)代码和特定于平台的代码之间的正确划分:没有灵丹妙药可以决定什么去哪里,只是[之间的一系列权衡=26=]、简单代码与高度参数化代码等
您似乎想要 Thread
class 的值语义并且想知道在哪里添加间接以使其可移植。所以你使用 pimpl 惯用语,和一些条件编译。
根据您希望构建工具的复杂性所在的位置,并且如果您希望尽可能保持所有低级代码的独立性,您可以执行以下操作:
在你高级别headerThread.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
选项。
很好奇,如果把这种情况设计成下面这样(紧贴主题)会是怎样的:
// 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 { ... };
这将自动导致当您创建 "generic" Thread
对象时,您也会自动获得一个依赖于平台的 class,它会自动设置其内部结构,并且可能具有平台特定操作,当然还有您的 PlatformAThread
class 可能源自具有您可能需要的共同事物的通用 Base
class。
您还需要将构建系统设置为自动识别特定于平台的目录。
此外,请注意,我倾向于创建 class 继承的层次结构,有些人反对这样做:https://en.wikipedia.org/wiki/Composition_over_inheritance