🤖📘🍻 Hooray! After 3 years of work, we've finally released a new ebook on design patterns! Check it out »

Abstract Factory Design Pattern in C++: Before and after

Trying to maintain portability across multiple "platforms" routinely requires lots of preprocessor "case" statements. The Factory pattern suggests defining a creation services interface in a Factory base class, and implementing each "platform" in a separate Factory derived class.

Before

The client creates "product" objects directly, and must embed all possible platform permutations in nasty looking code.

#include <iostream>
#define LINUX

using namespace std;

/**
 * Abstract base product.
 */
class Widget {
 public:
  virtual void draw() = 0;
};

/**
 * Concrete product family 1.
 */
class LinuxButton : public Widget {
 public:
  void draw() { cout << "LinuxButton\n"; }
};
class LinuxMenu : public Widget {
 public:
  void draw() { cout << "LinuxMenu\n"; }
};

/**
 * Concrete product family 2.
 */
class WindowsButton : public Widget {
 public:
  void draw() { cout << "WindowsButton\n"; }
};
class WindowsMenu : public Widget {
 public:
  void draw() { cout << "WindowsMenu\n"; }
};

/**
 * Here's a client, which uses concrete products directly.
 * It's code filled up with nasty switch statements
 * which check the product type before its use.
 */
class Client {
 public:
  void draw() {
#ifdef LINUX
    Widget *w = new LinuxButton;
#else // WINDOWS
    Widget *w = new WindowsButton;
#endif
    w->draw();
    display_window_one();
    display_window_two();
  }

  void display_window_one() {
#ifdef LINUX
    Widget *w[] = {
        new LinuxButton,
        new LinuxMenu
    };
#else // WINDOWS
    Widget *w[] = {
        new WindowsButton,
        new WindowsMenu
    };
#endif
    w[0]->draw();
    w[1]->draw();
  }

  void display_window_two() {
#ifdef LINUX
    Widget *w[] = {
        new LinuxMenu,
        new LinuxButton
    };
#else // WINDOWS
    Widget *w[] = {
        new WindowsMenu,
        new WindowsButton
    };
#endif
    w[0]->draw();
    w[1]->draw();
  }
};

int main() {
  Client *c = new Client();
  c->draw();
}

Output

LinuxButton
LinuxButton
LinuxMenu
LinuxMenu
LinuxButton

After

The client receives a platform-specific factory object, which encapsulates use of "new" operator. Client delegates all creation requests to this factory.

#include <iostream>
#define LINUX

using namespace std;

/**
 * Abstract base product. It should define an interface
 * which will be common to all products. Clients will
 * work with products through this interface, so it
 * should be sufficient to use all products.
 */
class Widget {
 public:
  virtual void draw() = 0;
};

/**
 * Concrete product family 1.
 */
class LinuxButton : public Widget {
 public:
  void draw() { cout << "LinuxButton\n"; }
};
class LinuxMenu : public Widget {
 public:
  void draw() { cout << "LinuxMenu\n"; }
};

/**
 * Concrete product family 2.
 */
class WindowsButton : public Widget {
 public:
  void draw() { cout << "WindowsButton\n"; }
};
class WindowsMenu : public Widget {
 public:
  void draw() { cout << "WindowsMenu\n"; }
};

/**
 * Abstract factory defines methods to create all
 * related products.
 */
class Factory {
 public:
  virtual Widget *create_button() = 0;
  virtual Widget *create_menu() = 0;
};

/**
 * Each concrete factory corresponds to one product
 * family. It creates all possible products of
 * one kind.
 */
class LinuxFactory : public Factory {
 public:
  Widget *create_button() {
    return new LinuxButton;
  }
  Widget *create_menu() {
    return new LinuxMenu;
  }
};

/**
 * Concrete factory creates concrete products, but
 * returns them as abstract.
 */
class WindowsFactory : public Factory {
 public:
  Widget *create_button() {
    return new WindowsButton;
  }
  Widget *create_menu() {
    return new WindowsMenu;
  }
};

/**
 * Client receives a factory object from its creator.
 *
 * All clients work with factories through abstract
 * interface. They don't know concrete classes of
 * factories. Because of this, you can interchange
 * concrete factories without breaking clients.
 *
 * Clients don't know the concrete classes of created
 * products either, since abstract factory methods
 * returns abstract products.
 */
class Client {
 private:
  Factory *factory;

 public:
  Client(Factory *f) {
    factory = f;
  }

  void draw() {
    Widget *w = factory->create_button();
    w->draw();
    display_window_one();
    display_window_two();
  }

  void display_window_one() {
    Widget *w[] = {
        factory->create_button(),
        factory->create_menu()
    };
    w[0]->draw();
    w[1]->draw();
  }

  void display_window_two() {
    Widget *w[] = {
        factory->create_menu(),
        factory->create_button()
    };
    w[0]->draw();
    w[1]->draw();
  }
};

/**
 * Now the nasty switch statement is needed only once to
 * pick and create a proper factory. Usually that's
 * happening somewhere in program initialization code.
 */
int main() {
  Factory *factory;
#ifdef LINUX
  factory = new LinuxFactory;
#else // WINDOWS
  factory = new WindowsFactory;
#endif

  Client *c = new Client(factory);
  c->draw();
}

Output

LinuxButton
LinuxButton
LinuxMenu
LinuxMenu
LinuxButton

Code examples

More info, diagrams and examples of the Abstract Factory design pattern you can find on our new partner resource Refactoring.Guru.