Chapter 12 : Series On Design Patterns – Factory Method

Key Words: Design patterns in C++, Factory method in C++, creational design pattern

Topics at a glance:

  • Factory Method and abstracting the creation
  • How to implement Factory method in the modern C++ way

Design patterns in C++

This chapter is the first part of a series on design patterns in C++. There are many important design patterns and so, I have decided to start a series on design patterns. I will discuss about some popular design patterns, their uses, C++ implementation, what all things we need to take care about their thread safety aspects, resource management and ownership aspect.

Creational design pattern: Factory method in C++

In this chapter, we will see factory method pattern, which is a creational design pattern, and what all we need to take care when using factories.

Factory method is simply a static class method that aids in the instantiation of objects particularly belonging to a hierarchical class system. i.e. Base class – derived class hierarchy.

This is important as factory method depends on “is a” relationship that I have mentioned in some previous chapters. i.e. any derived class instance is a base class instance. When it comes to it’s C++ implementation, we need to understand that “is a” identity works only with pointer or reference. For factory method we will work with the former i.e. pointer and not the later i.e. reference.

The example I am illustrating below, has a base-derived class system as depicted below:

Now, we need to write a shape_factory class which defines a static method which will create instances of circle or square depending upon the ‘type’ of shape we pass as parameter to this method.

Now, for fruitful use of this shape factory let us define a client that will test any shapes that it gets by simply drawing it. i.e. Client only bothers about the draw() member function defined for any shape instances.  To make sure any concrete derived classes of base class shape will implement their own variant of draw member function, I have made base class’s draw member function as ‘pure virtual’. Such a base class is also known as an abstract base class in C++. Client make use of this policy. Now, let us see all this in action illustrating a factory method design pattern.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <iostream>
#include <memory>

using namespace std;

class shape;

enum class shape_type{circle, square};

class shape_factory
{
public:
    static shape* create_shape(shape_type type);
};

// shape is an abstract class
class shape
{
public:
    virtual ~shape()
    {
        // do nothing 
    }
    virtual void draw() const = 0; // pure virtual 
    // cannot create instances of shape, 
    // thus shape is an abstract base class
};

class circle : public shape
{
private:
    float radius_;
public:
    explicit circle(float radius = 0.0F) : radius_{radius}
    {
        // do nothing
    }
    ~circle() 
    { 
        cout << "circle with radius " << radius_ << " destroyed" << endl;
    }
    
    void draw() const override
    {
        cout << "Am a circle with radius " << radius_ << endl;
    }
};

class square : public shape
{
private:
    float length_;
public:
    explicit square(float length = 0.0F) : length_{length} {}
    ~square()
    {
        cout << "square with length " << length_ << " destroyed" << endl;
    }
    void draw() const override
    {
        cout << "Am a square with length " << length_ << endl;
    }
};

shape* shape_factory::create_shape(shape_type type)
{
    switch(type)
    {
        case shape_type::circle:
        {
            float radius;
            
            cout << "Enter the radius for the new circle " << endl;
            cin >> radius;
            if(radius < 0.0F)
            {
                cout << "Bad radius entered. Taking default radius of 0.0F" << endl;
                radius = 0.0F;
            }
            
            return new circle(radius);
        }
        case shape_type::square:
        {
            float length;
            
            cout << "Enter the length for the new square " << endl;
            cin >> length;
            if(length < 0.0F)
            {
                cout << "Bad length entered. Taking default length of 0.0F" << endl;
                length = 0.0F;
            }
            
            return new square(length);
        }
    }// switch type ends here
}

void test_shape(shape_type type)
{
    // create a shape 
    // unique_ptr is very important
    unique_ptr<shape> shape_ptr(shape_factory::create_shape(type));
    // draw the shape
    shape_ptr->draw();
    
    return;
}

int main()
{
    // test circle
    test_shape(shape_type::circle);
    
    // test square
    test_shape(shape_type::square);
    
    cout << "Main ends here" << endl;
    
    return 0;
}

Here, everything is as I have told above, except for the use of unique_ptrs to manage ownership of the created shape instances.

NOTE: Factory method will release the ownership of created object immediately upon return and transfer it to the caller. It is caller’s responsibility to release the object instances’ memory back tofree store.

In this case client is the caller and it manage the ownership elegantly by making use of std::unique_ptr.

Now let us see the result:

Enter the radius for the new circle
1.25
Am a circle with radius 1.25
circle with radius 1.25 destroyed
Enter the length for the new square
7.5
Am a square with length 7.5
square with length 7.5 destroyed
Main ends here

Note that how the shapes are automatically destroyed once the client test_shapes() function returns.

NOTE: If you want shared ownership for the created objects from the factory, then use std::shared_ptr instead of std::unique_ptr.

Factory method hides the creation of objects. Usually client code will be written adhering to code to base class instance principle. Without factory, client will at least have to create specific instances of derived class objects, violating or compromising strict adherence to code to base principle. With factory method, client can fully comply to code to base class methodology.

Enjoyed the chapter? Let me know in the comments below. Thanks! 🙂

0

Leave a Reply