Hiding multiple implementations behind a single in

2019-05-07 16:49发布

问题:

I know about the Strategy and Abstract Factory design patterns - however they don't solve my current problem:

I'm creating a C++ library that offers a very basic GUI. However I want the user to be able to choose at compile time which GUI library to use (say Qt or FLTK) to actually render the GUI. The user should however only need to know about the methods in my library.

It should be possible to compile the same code without any changes using either a Qt backend or an FLTK backend.

I thought of something like:

class A
{
  // do things that are not specific to QT or FLTK here as there are many 
  // methods I will need independent of the backend
}

class QT_A : public A
{
  // Implement the actual creation of a window, display of a widget here using Qt
}

class FLTK_A : public A
{
  // Implement the actual creation of a window, display of a widget here using FLTK
}

The problem is that I do not want the user to know about QT_A or FLTK_A. The user (developer) should just deal with A. Also, I can't have both variants at the same time as I don't want my library to depend on both Qt and FLTK; just whichever was chosen at compile time.

回答1:

One option is the Pimpl idiom described in another answer.

Another option is a factory returning a pointer to the interface class:

std::unique_ptr<A> make_A()
{
#if defined(USING_QT)
    return std::unique_ptr<A>(new QT_A(...));
#elif defined(USING_FLTK)
    return std::unique_ptr<A>(new FLTK_A(...));
#else
    #error "No GUI library chosen"
#endif
}


回答2:

The Pimpl idiom may be an alternative. It allows you to create a common interface without framework dependent members.

class A
{
  struct impl;
  std::unique_ptr<impl> pimpl; // or scoped_ptr/auto_ptr on non-C++11
public:
  A();
  ~A();
 void do_sth();
};

Then, the source file can provide different implementations of impl depending on the backend.

#ifdef QT
struct A::impl : QWidget { // Make it polymorphic, if you need
  QImage img;
  QString data;
};

void A::do_sth()
{
  impl->make_it(); // full access to the Qt things
}

A::A()
  : pimpl(new ...)
{
}

A::~A() {} // no need for delete thanks the smart pointer

#endif


回答3:

No need of fancy patterns.

You distribute

  1. headers of A;
  2. a library that contains A, QT_A, and make_A function;
  3. another library that contains A, FLTK_A and another implementation of make_A function.

The user links to either library.