The Abstract Factory Design Pattern with Modern C++

The Gang of Four state that the abstract factory design pattern is used to “provide an interface for creating families of related or dependent objects without specifying their concrete classes.”

Abstract factories provide an interface for creating a family of products. By writing code that uses this interface we can decouple our code from the actual factory that creates the products. This allows us to implement a variety of factories that create products meant for different contexts. This decoupling aspect is what makes abstract factories so useful.

In the example below, adapted from riptutorial, we will look at the case of creating a GUI for Windows and Linux environments. In this example, the code is decoupled from the actual products, which means we can easily substitute different factories to get different behaviors. We create two concrete implementations of the GUIFactory class, which itself is composed of factory methods, one for windows operating systems, and another for linux operating systems. We then use the generic interface created by the GUIFactory class to create concrete objects.

Because this is modern C++, we use unique_ptr pretty liberally.

/* abstract_factory_example.cpp */
#include <iostream>
#include <memory>
#include <string>

/* GUIComponent abstract base class */
class GUIComponent {
public:
  virtual ~GUIComponent() = default;
  virtual void draw() const = 0;
};

class Frame  : public GUIComponent {};
class Button : public GUIComponent {};
class Label  : public GUIComponent {};
class ScrollBar : public GUIComponent {};

class LinuxFrame : public Frame {
public:
  void draw() const override {
    std::cout << "I'm a Linux frame" << std::endl;
  }
};

class LinuxButton : public Button {
public:
  void draw() const override {
    std::cout << "I'm a Linux button" << std::endl;
  }
};

class LinuxLabel : public Label {
public:
  void draw() const override {
    std::cout << "I'm a Linux label" << std::endl;
  }
};

class LinuxScrollBar : public ScrollBar {
public:
  void draw() const override {
    std::cout << "I'm a Linux scrollbar" << std::endl;
  }
};

class WindowsFrame : public Frame {
public:
  void draw() const override {
    std::cout << "I'm a Windows frame" << std::endl;
  }
};

class WindowsButton : public Button {
public:
  void draw() const override {
    std::cout << "I'm a Windows button" << std::endl;
  }
};

class WindowsLabel : public Label {
public:
  void draw() const override {
    std::cout << "I'm a Windows label" << std::endl;
  }
};

class WindowsScrollBar : public ScrollBar {
public:
  void draw() const override {
    std::cout << "I'm a windows scrollbar" << std::endl;
  }
};

/* Abstract factory abstract base class
 * Note: abstract factories can also be concrete
 */
class GUIFactory {
public:
  virtual ~GUIFactory() = default;
  /* create_frame factory method */
  virtual std::unique_ptr<Frame> create_frame() = 0;
  /* create_button factory method */
  virtual std::unique_ptr<Button> create_button() = 0;
  /* create_label factory method */
  virtual std::unique_ptr<Label> create_label() = 0;
  /* create_scrollbar factory method */
  virtual std::unique_ptr<ScrollBar> create_scrollbar() = 0;
  /* create static method to select which concrete factory to instantiate */
  static std::unique_ptr<GUIFactory> create(const std::string& type);
};

/* Concrete windows factory */
class WindowsFactory : public GUIFactory {
public:
  std::unique_ptr<Frame> create_frame() override {
    return std::make_unique<WindowsFrame>();
  }
  std::unique_ptr<Button> create_button() override {
    return std::make_unique<WindowsButton>();
  }
  std::unique_ptr<Label> create_label() override {
    return std::make_unique<WindowsLabel>();
  }
  std::unique_ptr<ScrollBar> create_scrollbar() override {
    return std::make_unique<WindowsScrollBar>();
  }
};

/* Concrete Linux factory */
class LinuxFactory : public GUIFactory {
public:
  std::unique_ptr<Frame> create_frame() override {
    return std::make_unique<LinuxFrame>();
  }
  std::unique_ptr<Button> create_button() override {
    return std::make_unique<LinuxButton>();
  }
  std::unique_ptr<Label> create_label() override {
    return std::make_unique<LinuxLabel>();
  }
  std::unique_ptr<ScrollBar> create_scrollbar() override {
    return std::make_unique<LinuxScrollBar>();
  }
};

/* create static method to select which type of factory to use */
std::unique_ptr<GUIFactory> GUIFactory::create(const std::string& type) {
  if (type == "windows") return std::make_unique<WindowsFactory>();
  return std::make_unique<LinuxFactory>();
}

/* build_interface function that takes in an abstract factory as a param*/
void build_interface(GUIFactory& factory) {
  auto frame = factory.create_frame();
  auto button = factory.create_button();
  auto label = factory.create_label();
  auto scrollbar = factory.create_scrollbar();

  frame->draw();
  button->draw();
  label->draw();
  scrollbar->draw();
}

int main(int argc, char *argv[]) {
  if (argc < 2) return 1;
  auto guiFactory = GUIFactory::create(argv[1]);
  build_interface(*guiFactory);
}

We’ll compile this quick with a simple g++ invocation:

g++ -o abstract_factory_example abstract_factory_example.cpp

Then, if we run the executable with command the line input character array ‘linux’:

./abstract_factory_example linux

we get the output:

I'm a Linux frame
I'm a Linux button
I'm a Linux label
I'm a Linux scrollbar

and if we run the executable with the input ‘windows’:

./abstract_factory_example windows

we get the output:

I'm a Windows frame
I'm a Windows button
I'm a Windows label
I'm a windows scrollbar