devxlogo

Use the Factory Pattern to Facilitate Dynamic Typing

Use the Factory Pattern to Facilitate Dynamic Typing

ood old object-oriented programming is still alive and kicking. In many C++ frameworks and applications where inheritance and virtual functions thrive you often need to create a derived object whose precise type can only be determined at runtime. Lean how Factory can boost your code’s reliability and performance.


Your application has to create a derived object but the exact type of that object isn’t known at compile-time. How do you implement a generic mechanism that creates the right type of object at runtime?


Use the Factory design pattern.

Spitting Images

Suppose you have an abstract class from which concrete classes are derived:

class Image //abstract{public: virtual void image_type() const=0; virtual ~Image()=default; //...};class JpgImage: public Image{public: virtual void image_type() const {cout

The only feature that needs to be explained is the =default specifier in the base class' destructor. Put simply, this specifier rids you of the burden (and potential performance overhead) of defining a manual empty destructor for the abstract base class, while still ensuring that the destructor is virtual (read more on defaulted functions here).

Next, assume that you're designing an image viewer that handles various image formats: .bmp, .jpeg, .png, and so on. The viewer can determine only at runtime which derived object it needs in order to display and edit the image that the user has selected. To do that, the viewer needs an engine (known as a factory) that creates the right type of object according to the file extension. For example, when the user clicks on an image with the .jpg extension the factory will create a JpgImageobject to handle this format.

Factory Class

Factory is implemented as an independent class with a static member function called create(). create() returns a pointer whose static type is that of the abstract base class. The dynamic typeof the returned pointer will reflect the actual object that has been created for viewing the image:

class ImageFactory {public:  static Image* create(const std::string& ext)   {      if (ext=="jpg")         return new JpgImage;      else if (ext=="bmp")         return new BmpIamge;      else //default          return new PngImage;  }};

Usage

Technically, you don't need to instantiate an ImageFactory object to call create() because it's a static member function. However, for the sake of an intuitive and cleaner syntax, you can still create a factory object, as shown in the following code listing. Additionally, it's best to use a smart pointer to ensure automatic cleanup of object created by the factory class. Smart pointers such as std::tr1::shared_ptr, std::auto_ptr, and its successor, std::unique_ptr, also offer the convenience of the member function reset()which disposes of an image object and binds a new object to the same smart pointer in one shot:

int main(){  ImageFactory factory;  std::auto_ptr img(factory.create("jpg"));  img->image_type(); //output: "jpg image"  img.reset(factory.create("bmp"));  img->image_type();//output: "bmp image"  img.reset(factory.create("")); //default  img->image_type(); //output: "png image"}

Factory Refactored

Although the orthodox Factory pattern uses string comparisons to determine the actual derived object, there is a cleaner and more efficient way to achieve the same results. Replacing the string argument of create() with an enumeration will produce significantly faster code and enable you to use a switch which is more readable and easier to maintain than a list of else-ifstatements.

First, add an enumeration into the Image Factory class:

class ImageFactory{public:  enum ImgFormat   {    JPG,    BMP,    //...add new image formats as necessary    DEFAULT   };};

Next, add a switch block to create():

 static Image* create(ImgFormat fmt){  switch (fmt)  {   case JPG:     return new JpegImage;     break;   case  BMP:     return new BmpImage;     break;   default:     return new PngImage;  }}

Finally, change main()accordingly:

int main(){ ImageFactory factory; auto_ptr img(factory.create(ImgFactory::JPG)); img->image_type(); img.reset(factory.create(ImageFactory::BMP)); img->image_type(); img.reset(factory.create(ImageFactory::DEFAULT));  img->image_type();}

A Timeless Solution

Recently, I've seen several questions in the C++ forumregarding techniques for creating derived objects at runtime. In all these cases, Factory has proved to be the right solution. This only goes to show that classic design patterns are still as relevant as ever.

devx-admin

Share the Post: