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
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
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
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:
#define DECLAREFORMAT(T) const static bool -
REGISTEREDWITH FACTORY
#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:
class FormatMP3 : public KnownFormat {
private :
DECLARE_FORMAT ( FormatMP3 );
};
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
#ifndef _KNOWN_FORMAT_H_
#define _KNOWN_FORMAT_H_
#include <string>
class KnownFormat {
public :
virtual std::string getFormatName () const = 0;
virtual ~KnownFormat () {};
};
#endif // _KNOWN_FORMAT_H
FormatMP3.h
#ifndef _FORMAT_MP3_
#define _FORMAT_MP3_
#include "knownformat.h"
#include "knownformattemplatedfactory.h"
#include <string>
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_FORMAT (FormatMP3);
};
#endif // _FORMAT_MP3_
FormatMP3.cc
#include "formatmp3.h"
#include "wrongformatexception.h"
#include "filenotfoundexception.h"
#include <vector>
#include <fstream>
IMPLEMENT_FORMAT (FormatMP3);
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>
class KnownFormatAbstractFactory {
public:
virtual KnownFormatHandle create ( const std::
string &filename ) const = 0;
virtual ~KnownFormatAbstractFactory ( ) {}
};
#endif // _KNOWN_FORMAT_ABSTRACT_FACTORY_H_
KnownFormatTemplatedFactory.h
#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 />
#define DECLARE_FORMAT(T) const static bool REGISTERED_WITH_FACTORY
#define IMPLEMENT_FORMAT(T) const bool T::REGISTERED_WITH_FACTORY = \
KnownFormatFactory::inst().addFormat( \
KnownFormatAbsFactoryHandle(new KnownFormatTemplatedFactory<t />()))
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
#ifndef __KNOWN_FORMAT_FACTORY_H__
#define __KNOWN_FORMAT_FACTORY_H__
#include "types.h"
#include "knownformatabstractfactory.h"
#include <vector>
#include <string>
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
#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 &) {
}
}
throw UnknownFormatException(filename);
}
KnownFormatFactory &KnownFormatFactory::inst () {
static KnownFormatFactory instance;
return instance;
}
Types.h
#ifndef __TYPES_H__
#define __TYPES_H__
#include <tr1/memory>
class KnownFormat;
class KnownFormatAbstractFactory;
typedef std::tr1::shared_ptr<KnownFormat>
KnownFormatHandle;
typedef std::tr1::shared_ptr<KnownFormatAbstractFactory>
KnownFormatAbsFactoryHandle;
#endif // __TYPES_H__
FileNotFoundException.h
#ifndef __FILE_NOT_FOUND_EXCEPTION_H__
#define __FILE_NOT_FOUND_EXCEPTION_H__
#include <string>
#include <stdexcept>
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
#ifndef __WRONG_FORMAT_EXCEPTION_H__
#define __WRONG_FORMAT_EXCEPTION_H__
#include <string>
#include <stdexcept>
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
#ifndef __UNKNOWN_FORMAT_EXCEPTION_H__
#define __UNKNOWN_FORMAT_EXCEPTION_H__
#include <string>
#include <stdexcept>
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
#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;
}
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.