Click here to Skip to main content
15,868,141 members
Articles / Programming Languages / C++

The Dynamic Registry Factory

Rate me:
Please Sign up or sign in to vote.
4.40/5 (7 votes)
6 Jan 2010CPOL5 min read 25.2K   102   9   4
The idea was to write the system in such a way that every format will have a common interface, and adding (or removing) a new format will not a affect the existing code at all.

Contents

Introduction

This paper will discuss a specic design problem I've come across in the course of a hobby programming project, and the solution I have worked out for it. The solution can be viewed as a specic implementation of what can be considered as a far more general design pattern, which I've decided to call "The dynamic registry factory" (I'm open to suggestions of better names at this point).

The suggested solution will be introduced with thorough explanation of its ins and outs. I will of course admit it is not perfect - it can surely be tweaked and generalized further, but this solution and implementation demonstrate the general idea very well, and account for a good showcase.

The Problem

I had to write a program that will be able to handle many different file formats of various types (MP3, WAV, BMP, etc..) in a very robust, general, and easily extensible manner. The idea was to write the system in such a way that every format will have a common interface, and adding (or removing) a new format will not a affect the existing code at all.

The solution suggested in this paper answers all of the presented requirements, and more. It supports a subset of the original interface: Just enough functionality to allow us to name the correct file format - much like the file command under Linux. It is of course possible to extend this interface at will.

Suggested Design

Known Formats

image001.png
Figure 1: KnownFormat and FormatMP3 class diagrams.

Every implemented file format in the system has to implement a common KnownFormat interface. This interface supports a single method to retrieve the name of the format.

Every implementing class (FormatMP3 implementation is included as an example) will throw a WrongFormatException exception if the given file does not match the implemented format. The construction succeeds if and only if the given file is in the correct format.

Factories for Known Formats

image002.png
Figure 2: KnownFormatAbstractFactory and KnownFormatTemplatedFactory class diagrams.

The classic factory design pattern is put to good use here: KnownFormatAbstractFactory defines the common interface for any factory class that is able to generate a KnownFormat object upon receiving a filename. We supply a generic (templated) implementation of a factory for any KnownFormat, with two very useful MACROs that will be introduced in the implementation chapter. The KnownFormatTemplatedFactory is templated on a concrete file format type and provides a functionality of creating a new object of that KnownFormat upon invocations of the create() method. It implements the KnownFormatAbstractFactory interface for an arbitrary KnownFormat implementation.

The Dynamic Registry Factory

image003.png
Figure 3: KnownFormatFactory class diagram

The KnownFormatFactory is the implementation of the dynamic registry factory. Through invocation of the addFormat() method, which is essentially the gateway to the registration process, it allows easy registration of new supported formats at any given time, with incredible ease of use and without affecting any existing code (using the aforementioned two macros introduced below, in the implementation section). Once the create() method is invoked, the factory goes over all registered formats and attempts to generate a new KnownFormat object through them. An UnknownFormatException will be thrown if the file format could not be matched with any supported file format.

Implementation

As you have seen, the KnownFormatFactory class allows for any concrete format factory to register on the fly, and the KnownFormatTemplatedFactory allows automatic code generation of a factory for any concrete FileFormat. Combining these two facts, we are able to carry out the registration process by using the two following macros:

C++
// taken from knownformat templatedfactory.h
// include folloing macro within class definition 
#define DECLAREFORMAT(T) const static bool  - 
    REGISTEREDWITH FACTORY 
// inc lude following macro in implementat ion file 
#define IMPLEMENTFORMAT(T) const bool T:: - 
    REGISTEREDWITH FACTORY = 
        KnownFormatFactory :: inst ( ) . addFormat (
        KnownFormatAbsFactoryHandle (
        new KnownFormatTemplatedFactory<T>()))

Exploiting the fact that static const members are initialized on the loading of the program, we define a boolean member, which registers with the KnownFormatFactory upon initialization. This nifty trick allows every concrete format to register by using these two very simple macros. To illustrate further, heres how FormatMP3 is implemented:

C++
//  taken from formatmp3.h
class FormatMP3 : public KnownFormat {
     // ...
     private : 
         DECLARE_FORMAT ( FormatMP3 ); 
}; 

//  taken from formatmp3.cc
IMPLEMENT_FORMAT ( FormatMP3 );

This is crystal clear, simple, maintainable, and easily reusable. The rest of the implementation can be found included with this paper, and is more or less straightforward.

Future Directions

Thread Safety

The propsed design is not thread safe. In order for the design to function correctly in a multi-threaded environment, some minor changes must be made to the KnownFormatFactory class:

  • A thread safe singleton implementation should be used.
  • Proper locking mechanisms should be used within its member functions.

Epilogue

To conclude, I would like to point out the pros and cons of the suggested technique.

Pros

  • Adding support for new formats does not require altering existing code nor recompilation.
  • Adding support for a new format is transparent for all users.
  • It is possible to load formats on the fly, even in a lazy manner (only adding formats when needed). It is possible to load shared (dynamically linked) libraries containing more formats as well.
  • Very scalable design.
  • It is possible (and will not require much effort) to change the registration process such that every format will be registered with its name, to allow lookup of specific formats. For example, this feature can be utilized to implement a save option with many supported formats.

Cons

  • This solution forces all supported file formats to implement the same interface, which can be limiting at times (although one could use dynamic cast, it is surely not recommended).

Feel free to observe the enclosed implementation. It contains more details that were omitted from the article itself, but may provide better insight of the proposed design.

Source Code

KnownFormat.h

C++
#ifndef _KNOWN_FORMAT_H_ 
#define _KNOWN_FORMAT_H_

#include <string> 
/** 
* This is an abstract class (interface) representing
* a file whose format is known and can be identified.
* It is possible to easily extend this interface in
* the future, to offer broader functionality. 
*/ 
class KnownFormat {
    public : 
        virtual std::string getFormatName () const = 0; 
        
        virtual ~KnownFormat () {}; 
}; 
#endif // _KNOWN_FORMAT_H

FormatMP3.h

C++
#ifndef _FORMAT_MP3_
#define _FORMAT_MP3_ 

#include "knownformat.h" 
#include "knownformattemplatedfactory.h" 

#include <string>
/** 
* This class implements the MP3 file format . 
*/ 
class FormatMP3 : public KnownFormat {
    public:
        FormatMP3 (const std::string &filename );
        
        virtual std::string getFormatName ( ) const ; 
    
    protected:
        const static std::string MAGIC; 
        const static std::string NAME; 
        
    private:
        // declare registration
        DECLARE_FORMAT (FormatMP3); 
}; 
#endif // _FORMAT_MP3_

FormatMP3.cc

C++
#include "formatmp3.h" 
#include "wrongformatexception.h" 
#include "filenotfoundexception.h" 

#include <vector>
#include <fstream> 

// just implement the registration.
IMPLEMENT_FORMAT (FormatMP3); 

// http://www.garykessler.net/library/filesigs.html 
const std::string FormatMP3::MAGIC = "ID3" ; 
const std::string FormatMP3::NAME = "MP3" ; 

FormatMP3::FormatMP3 (const std::string &filename) {
    std::ifstream ifs (filename.c_str());
    if (!ifs) 
        throw FileNotFoundException (filename);
    std::vector<char> read (MAGIC.size(), 0);
    
    ifs.read(&read [0], read.size()); 
    if (std::string(&read[0], read.size()) != MAGIC)
        throw WrongFormatException (filename, NAME); 
}

std::string FormatMP3::getFormatName () const {
    return NAME; 
}

KnownFormatAbstractFactory.h

#ifndef _KNOWN_FORMAT_ABSTRACT_FACTORY_H_ 
#define _KNOWN_FORMAT_ABSTRACT_FACTORY_H_

#include "types.h" 
#include "knownformat.h" 

#include <string> 

/** 
* This is an abstract class (interface) representing
* the common interface to any factory class able to
* create a KnownFormat instance from a given filename.
*/ 
class KnownFormatAbstractFactory {
    public: 
        virtual KnownFormatHandle create ( const std::
            string &filename ) const = 0; 

        virtual ~KnownFormatAbstractFactory ( ) {}
}; 

#endif // _KNOWN_FORMAT_ABSTRACT_FACTORY_H_

KnownFormatTemplatedFactory.h

C++
#ifndef __KNOWN_FORMAT_TEMPLATED_FACTORY_H__
#define __KNOWN_FORMAT_TEMPLATED_FACTORY_H__

#include "types.h"
#include "knownformat.h"
#include "knownformatfactory.h"
#include "knownformatabstractfactory.h"

#include <string />

// include following macro within class definition
#define DECLARE_FORMAT(T) const static bool REGISTERED_WITH_FACTORY
// include following macro in implementation file
#define IMPLEMENT_FORMAT(T) const bool T::REGISTERED_WITH_FACTORY = \
    KnownFormatFactory::inst().addFormat( \
    KnownFormatAbsFactoryHandle(new KnownFormatTemplatedFactory<t />()))

/**
 * Templated implementation of KnownFormatAbstractFactory
 * The only requiremnt of type T is to have a constructor
 * with the filename as a parameter.
 * The constructor may throw WrongFormatException. 
 */
template <typename T>
class KnownFormatTemplatedFactory : public KnownFormatAbstractFactory {
    public:
        virtual KnownFormatHandle create (const std::string &filename) const {
            return KnownFormatHandle(new T(filename));	
        }
};

#endif // __KNOWN_FORMAT_TEMPLATED_FACTORY_H__

KnownFormatFactory.h

C++
#ifndef __KNOWN_FORMAT_FACTORY_H__
#define __KNOWN_FORMAT_FACTORY_H__

#include "types.h"
#include "knownformatabstractfactory.h"

#include <vector>
#include <string>

/**
 * This class is a singleton, holding an isntance of 
 * every format factory in the system.
 * Given a filename it is able to go over all known 
 * formats and generate a KnownFormat object. 
 */
class KnownFormatFactory {
    public:
        bool addFormat (KnownFormatAbsFactoryHandle factory);

        KnownFormatHandle create (const std::string &filename) const;

        static KnownFormatFactory &inst ();

    private:
        typedef std::vector<KnownFormatAbsFactoryHandle> FactoryVector;
        FactoryVector m_factories;

        KnownFormatFactory () {}
        KnownFormatFactory (const KnownFormatFactory &f) {}
        KnownFormatFactory &operator= (const KnownFormatFactory &f);
};

#endif // __KNOWN_FORMAT_FACTORY_H__

KnownFormatFactory.cc

C++
#include "knownformatfactory.h"
#include "wrongformatexception.h"
#include "unknownformatexception.h"

bool KnownFormatFactory::addFormat (const KnownFormatAbsFactoryHandle factory) {
    m_factories.push_back(factory);
    return true;
}

KnownFormatHandle KnownFormatFactory::create (const std::string &filename) const {
    for (FactoryVector::const_iterator it=m_factories.begin();it != m_factories.end();++it) {
        try {
            return (*it)->create(filename);
        }
        catch (const WrongFormatException &) {
            // next one..
        }
    }
    throw UnknownFormatException(filename);
}

KnownFormatFactory &KnownFormatFactory::inst () {
    static KnownFormatFactory instance;
    return instance;
}

Types.h

C++
#ifndef __TYPES_H__
#define __TYPES_H__

#include <tr1/memory>

// forward decl
class KnownFormat;
class KnownFormatAbstractFactory;

// defining the common handlers (shared pointers)
typedef std::tr1::shared_ptr<KnownFormat>
    KnownFormatHandle;
typedef std::tr1::shared_ptr<KnownFormatAbstractFactory>
    KnownFormatAbsFactoryHandle;

#endif // __TYPES_H__

FileNotFoundException.h

C++
#ifndef __FILE_NOT_FOUND_EXCEPTION_H__
#define __FILE_NOT_FOUND_EXCEPTION_H__

#include <string>
#include <stdexcept>

/**
 * This exception will be thrown when a given filename
 * could not be found.
 */
class FileNotFoundException : public std::runtime_error {
    public:
        FileNotFoundException (const std::string &filename) 
            : std::runtime_error("Could not find: " + filename) {}
};

#endif // __FILE_NOT_FOUND_EXCEPTION_H__

WrongFormatException.h

C++
#ifndef __WRONG_FORMAT_EXCEPTION_H__
#define __WRONG_FORMAT_EXCEPTION_H__

#include <string>
#include <stdexcept>

/**
 * This exception should be thrown by a KnownFormat 
 * implementation when the given filename isn't of 
 * that specific format.
 */
class WrongFormatException : public std::runtime_error {
    public:
        WrongFormatException (const std::string &filename, const std::string &formatname) 
            : std::runtime_error("File " + filename + " does not match format " +
                formatname) {}
};

#endif // __WRONG_FORMAT_EXCEPTION_H__

UnknownFormatException.h

C++
#ifndef __UNKNOWN_FORMAT_EXCEPTION_H__
#define __UNKNOWN_FORMAT_EXCEPTION_H__

#include <string>
#include <stdexcept>

/**
 * This exception will be thrown by the 
 * KnownFormatFactory class when a filename is of an 
 * unknown format.
 */
class UnknownFormatException : public std::runtime_error {
    public:
        UnknownFormatException (const std::string &filename) 
            : std::runtime_error("File " + filename + " is of an unknown format") {}
};

#endif // __UNKNOWN_FORMAT_EXCEPTION_H__

Main.cc

C++
#include "types.h"
#include "knownformat.h"
#include "knownformatfactory.h"

#include <string>
#include <vector>
#include <iostream>
#include <stdexcept>

int main () {
    std::vector<std::string> f;
    f.push_back("data/bad.mp3");
    f.push_back("data/good.mp3");
    f.push_back("data/none.mp3");

    std::vector<std::string>::iterator it = f.begin();
    std::vector<std::string>::iterator end = f.end();
    for (;it != end;++it)
        try {
            KnownFormatHandle formatted = 
                KnownFormatFactory::inst().create(*it);
            std::cout << *it << " is: " <<
                formatted->getFormatName() <<
                std::endl;
        }
    catch (const std::exception &e) {
        std::cout << "[info] " << e.what() << std::endl;
    }

    return 0;
}

License

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


Written By
Software Developer
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 2 Pin
Alexandre GRANVAUD7-Jan-10 21:38
Alexandre GRANVAUD7-Jan-10 21:38 
couldn't get the aim of this paper
GeneralArticle's motivation and goals. Pin
Roman Kecher8-Jan-10 1:38
Roman Kecher8-Jan-10 1:38 
GeneralMy vote of 1 Pin
haadikhan27-Jan-10 8:49
haadikhan27-Jan-10 8:49 
GeneralRe: My vote of 1 Pin
Pete O'Hanlon7-Jan-10 9:22
subeditorPete O'Hanlon7-Jan-10 9:22 

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

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