Click here to Skip to main content
Click here to Skip to main content

Abstract Factory Step-by-Step Implementation in C++

, 31 Mar 2014
Rate this:
Please Sign up or sign in to vote.
Step by Step Implementation of Abstract Factory Design Pattern in C++

Introduction

Abstract Factory is an absolute flagship of software design patterns. It starts of the list of 23 patterns in “Design Patterns - Elements of Reusable Code” classic book, it shows up in many software interview questions and provides the key to much of modern technologies, from COM objects to web-services.

The reason it's so important is it's efficiency in breaking apart dependencies and reducing coupling.

Numerous books and articles cover the different aspects of the pattern in great detail. However, assembling these ideas into a working implementation remains mostly up to you. In this article I'd like to present my personal take on Abstract Factory in C++ from start to finish.

Setting

Every implementation starts with an interface. For my example, I've decided to implement an inheritance-tree of geometric shapes, so the parent will look something like this:

namespace Common
{
    class IShape
    {
    public:
        virtual double GetArea() const = 0;

        // More virtual functions here

        virtual ~IShape() {};
    };
}
Next, we will implement a couple of concrete shape classes:
// Rectangle.h
..
namespace SimpleShapes
{
    class Rectangle : public Common::IShape
    {
    public:
        Rectangle(double width, double height);

        Rectangle(rapidxml::xml_node<>* node);

        virtual double GetArea() const;

    private:
        double _width;
        double _height;
    };
}

// Rectange.cpp
..
namespace SimpleShapes
{
    double Rectangle::GetArea() const
    {
        return _height * _width;
    }

    Rectangle::Rectangle(double width, double height) 
        : _width(width), _height(height)
    {

    }

    Rectangle::Rectangle(rapidxml::xml_node<>* node)
        : _width(0), _height(0)
    {
        rapidxml::xml_attribute<>* pWidthAttr = node->first_attribute("Width");
        _width = atof(pWidthAttr->value());

        rapidxml::xml_attribute<>* pHeightAttr = node->first_attribute("Height");
        _height = atof(pHeightAttr->value());
    }
}

Note that all concrete implementations support construction from XML. I've chosen RapidXML for parsing. It's easy to use and has the added benefit of header-only distribution.

Our goal will be to hide the concrete methods for construction of sub-classes from XML, so that the client will not need to know or care about them.

The Motivation

Before diving into the solution, let's recap why would we want a generic mechanism for constructing sub-classes in the first-place.

  1. It will (hopefully) enable us and the community to add new shapes even after deployment as plug-ins, without changing any client code.
  2. It will enable us to augment our implementations with new functionality such as logging, thread-safety, performance monitoring and more using the Decorator pattern
  3. It will enable us to test our code better by providing mock implementations when needed (Dependency Injection)
  4. In general, it will reduce coupling between different shape implementations and client code

Shape Factory Class

Abstract Factory is commonly implemented as a Singleton:

// ShapeFactory.h
..
namespace Common
{
    class ShapeFactory
    {
    public:
        static ShapeFactory& Instance();

        IShape * Create(rapidxml::xml_node<> * node) const;
    };
}
// ShapeFactory.cpp
..
ShapeFactory& ShapeFactory::Instance()
{
    static ShapeFactory factory;
    return factory;
}

When would you not implement Factory as a Singleton?

Consider our little shape project. We might want to have multiple implementations for each of our shapes, using different 3rd Party libraries. For example, in some parts of the program we will want the underlying implementation to rely on boost::geometry, while in others we might prefer more powerful CGAL implementation. In addition, our unit-tests might benefit from mock implementations for some of the test cases.
This can be accomplished by specifying the desired strategy in some global configuration. Alternatively, you might prefer to have multiple factories.

Perfectly Legal Solution

The simplest way to implement Create is:

IShape* ShapeFactory::Create(rapidxml::xml_node<> * node) const
{
    if (key == "Circle") return new Circle(node);
    else if (key == "Rectangle") return new Rectangle(node);
    // ...
    else throw new std::exception("Unrecognized object type!");
}

Don't be mistaken – this is perfectly legal implementation of the pattern.
It's is limited (no run-time plug-ins since all sub-classes have to be hard-coded) but it's functional.
My biggest concern with this solution is that adding or removing a sub-class involves changing factory implementation. This adds to the list of rituals and rain dances your team already has to practice, and in my experience, will almost always lead to (stupid) bugs.

Inversion of Control

Instead of the factory depending on all of the different implementations, we could make all implementations inform the factory how to construct themselves. This will allow the factory to be extended with new sub-classes, and each sub-class to be self-contained.

So how do we translate “a way to construct itself” to code? There is no reflection so simply passing the class-name won't cut it. When implementing any sort of events, listeners or callbacks in C++ traditionally you can go down one of two paths:

  1. C-style: use function-pointers
  2. OOP-style: use polymorphism

While you can absolutely make an extensible factory out of function-pointers I personally can't stand them (for implementation based on function-pointers see “C++ API Design”. C++ 11 standard gives new life to functional solution with function element - check out this CodeProject article)

So, I would much rather define a new interface:

namespace Common
{
    class IShapeMaker
    {
    public:
        virtual IShape * Create(rapidxml::xml_node<> * node) const = 0;
        virtual ~IShapeMaker() {}
    };
}
This interface will encapsulate all details of calling the concrete constructor. We can extend our factory to take advantage it:
//ShapeFactory.h
class ShapeFactory
{
public:
    ..
    void RegisterMaker(const std::string& key, IShapeMaker * maker);

private:
    std::map<std::string, IShapeMaker*> _makers;
};

//ShapeFactory.cpp
void ShapeFactory::RegisterMaker(const std::string& key, IShapeMaker* maker)
{
    if (_makers.find(key) != _makers.end())
    {
        throw new std::exception("Multiple makers for given key!");
    }
    _makers[key] = maker;
}
IShape* ShapeFactory::Create(rapidxml::xml_node<> * node) const
{
    std::string key(node->name());
    auto i = _makers.find(key);
    if (i == _makers.end())
    {
        throw new std::exception("Unrecognized object type!");
    }
    IShapeMaker* maker = i->second;
    return maker->Create(node);
}

You must be wondering – who will implement all the concrete shape makers and when they will get registered to the factory?

The Shape-Makers

For this solution to work, every concrete shape class must provide a shape-maker aside him, looking something like that:

// Circle.h
class CircleMaker : public IShapeMaker
{
public:
    virtual IShape * Create(rapidxml::xml_node<> * node) const
    {
        return new Circle(node);
    }
};
In fact all the shape-makers will look exactly like that, with the only difference being the name of the class being constructed. This sounds like a job for templates:
// ShapeMaker.h
template<typename T>
class ShapeMaker : public IShapeMaker
{
public:
    virtual IShape * Create(rapidxml::xml_node<> * node) const
    {
        return new T(node);
    }
}; 

Using this small template we can avoid manually writing a class for every shape in our project.

This is the way we register a maker with the factory:

ShapeFactory::Instance().RegisterMaker("Circle", new ShapeMaker<Circle>()); 
Since we are still obligated to register every shape maker, the shorter the registration process is the better. We can slice couple of extra tokens by letting the maker “take care of itself”:
template<typename T>
class ShapeMaker : public IShapeMaker
{
public:
    ShapeMaker(const std::string& key)
    {
        ShapeFactory::Instance().RegisterMaker(key, this);
    }
    ..
}; 
This way we can reduce the maker registration to just throwing a maker in some static context:
// Circle.cpp
static Common::ShapeMaker<Circle> maker("Circle");

This is a critical point: up until now it was unclear who is responsible for executing the code that registers the makers. Putting this code inside main would shatter any dreams of making sub-classes autonomous. We would just move the problem from factory implementation to main.

We get around this problem by using the fact that C++ run-time will initialize any static variable when the module is loaded. This is a great way to add functionality to module's main or dllmain function.

Here the fact that our factory is implemented as a singleton comes into play. Unless you get singleton implementation right, you will find yourself in trouble. Say we would declare _makers a static data member instead of using Instance function. What would happen is some of the makers would request to register themselves before _makers map had a chance to initialize. This, of course, would end up badly.

To smooth things up even more, we cound declare a macro:

#define REGISTER_SHAPE(T) static Common::ShapeMaker<T> maker(#T);

so that static Common::ShapeMaker<Circle> maker("Circle"); would become REGISTER_SHAPE(Circle);

Extensible Architecture

We can split our code into three functional parts:

  1. Common Infrastructure – contains everything you need to start working with shapes: IShape, IShapeMaker, ShapeMaker and the ShapeFactory
  2. Shape Implementations – contains the concrete sub-classes, depends only on Common Infrastructure.
  3. Client Code – consumes the functionality of our library by using Common Infrastructure to construct new shapes. Does not depend on Shape Implementations.

We have successfully decoupled the Client Code from the Implementations. What we can do now is to put each part in a different module. This way we can send our tiny Common.dll with some .h and .lib files to our partners and have them inherit from IShape and enrich our framework:

Factory Design

This sounds lovely in theory however the minute you put your implementations in an isolated module you'll encounter a problem: Despite the fact you've compiled and liked your test app against the implementations project, the factory will fail to construct any classes from that project.

If you check the Modules window you'll see that implementations modules hasn't been loaded and hence constructors of static variables have never been executed:

modules

The way I propose to solve this problem is by adding a new feature to our Factory implementation:

IShape* ShapeFactory::Create(rapidxml::xml_node<> * node) const
{
    rapidxml::xml_attribute<>* pAttr = node->first_attribute("LibraryName");
    if (pAttr != NULL)
    {
        std::string libraryName(pAttr->value());
        LoadLibrary(libraryName.c_str());
    }
    ..
}
Here's how you use it:
void main()
{
    char * str = "<Rectangle LibraryName=\"SimpleShapes.dll\" Width=\"5.3\" Height=\"3.7\" />";
    std::string content(str);

    xml_document<> doc;
    doc.parse(&content[0]);

    auto shape = ShapeFactory::Instance().Create(doc.first_node());
    auto area = shape->GetArea();
}

This way you can introduce new functionality to your project without re-compiling the client-app. It's important to note that using LoadLibrary opens you up to potential security risks.

Room for Improvement

While the implementation in it's current state is fully functional, there are a couple of things we could tighten up:

  1. Binary Compatibility – since I didn't bother to hide the implementation details from potential client code, 3rd Party plug-ins compiled with different version of C++ run-time or just different compilation parameters might not be compatible with existing binaries. The issue of binary-compatibility in C++ is complex, ultimately taking you towards using COM, but you can solve some of the challenges by hiding the implementation behind an interface or using the PIMPL idiom.

  2. Thread-Safety – there are numerous articles on ensuring thread-safety of Singleton implementation and thread-safety in general. You are also presented with a variety of threading and synchronization libraries to choose from.

  3. Ownership Management – depending on your preferred resource management strategy, you might want the factory to return a smart pointer of some sort. Similar to threading, you are presented with a wealth of options like reference counting, RAII wrapping and so on.

  4. Generalization – our Abstract Factory is quite concrete. It does the job of converting RapidXML nodes into Shapes, and that's it. Tomorrow you might need a factory that receives some other type of input, and behaves in slightly different way. You might end up with quite a lot of duplicate code. However, creating an elegant generalization can be challenging. I would strongly suggest “Modern C++ Design” to anyone interested

You can download the full source code directly from here

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

dorodnic
Software Developer (Senior)
Israel Israel
Hi, I'm Sergey.
I've worked in software development for the past six years, combining native C++ and managed C# programming.

Comments and Discussions

 
Questionwhere's the link to the code PinmemberJohnWallis4229-Mar-14 17:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 31 Mar 2014
Article Copyright 2014 by dorodnic
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid