Some Creational Design Patterns
00 - Table of Contents
01 - Introduction
02 - Factory Method
03 - Abstract Factory
04 - Builder
05 - Singleton
06 - Other Creational Patterns
01 - Introduction
The way objects are created is an important step in their life cycle. In most case, instanciation will even influence the behaviour of the object, that's just why this critical operation must be well designed if you want your frameworks to be as flexible as an old-skool jelly vector cube.
In C++, you basically have two ways to instanciate classes:
- STATIC, objects are stored in the data segment or on the execution stack when the instanciation comes in the code.
- DYNAMIC, memory is runtime allocated by the "new" operator called in the code. This way allows late binding. But warning, since no garbage collection is done, you'll have to cope with memory deallocation issues.
Here are some examples of instanciations in C++:
Foo foo1; Foo *foo2 = new Foo; Foo *foo3 = new Foo [100];Most of the time, these basic instanciation schemes are sufficient to bring objects to life. But when you have to deal with complex object architectures, it may be useful to write classes which will behave as "object factories".
02 - Factory Method
The aim is to define an interface to create objects whose type is defined by sub-classes. This is pattern is also often refered as a Virtual Constructor (no connection with C++'s keyword "virtual").
The following (ugly) ASCII OMT-like chart shows the structure of the Factory
Method Pattern.
--------- --------- | Product | | Creator | --------- --------- | | /_\ . . . . . . Inheritance /_\ | | ----------------- ----------------- | ConcreteProduct | <----------------- | ConcreteCreator | ----------------- . ----------------- . InstanciationExample:
// Creator.h
class Product {
public:
virtual void common_behaviour();
};
class Creator {
public:
virtual Product *create_product( Product_id id );
};
// Creator.cc
Product *Creator::create_product( Product_id id )
{
if ( id == ID_1 )
return new Product1( );
if ( id == ID_2 )
return new Product2( );
// ...
if ( id == ID_N )
return new ProductN( );
}
With this system, you can easily change the behaviour of the factory and
extends the number of id's simply by subclassing the Creator class.
You can also multiply the number of factory methods if there is no common
base. On the other hand, if you have a set of similar constructors (e.g. default
constructors), you can use templates to build concrete creators:
// We keep the Creator/Product classes from previous example
template class Generic_Creator : public Creator {
public:
Product *create() { return new A_Product; }
};
// Force compiler to build a class to create Super_Product
template class Generic_Creator Super_Creator;
Using factory methods also allows you to guarantee the way objects are create
and it's usually a good place to write a robust error handling system. I used
a bunch of Factory Methods to create the messages for a Networked application.
That way, I was sure that no one could create weird messages since the only
data structures allowed by my custom socket class was a Message object created
by the Message Factory.
03 - Abstract Factory
The Abstract Factory Pattern is used to provide an interface to create a FAMILY of objects without having to specify their concrete (real) class.
Example: As you are a kind scener, you've decided to write portables demos for
both Win32 and X11. So you need to deal with several objects.
--------------
| Factory |
|--------------|
|make_window() |
|make_palette()|
--------------
|
instanciates /_\
. |
. +-----------------+
. | |
. -------------- --------------
. | X11_Factory | | Win_Factory |
. |--------------| |--------------|
X11_Window <----|make_window() | |make_window() |----> Win_Window
X11_Palette <----|make_palette()| |make_palette()|----> Win_Palette
-------------- --------------
-------- -----------
| Window | | Palette |
|--------| |-----------|
|open() | |set_color()|
|close() | |update() |
|resize()| |invert() |
| ... | | ... |
-------- -----------
| |
/_\ /_\
| |
+------------+ +---------------+
| | | |
---------- ---------- ----------- -----------
|X11_Window| |Win_Window| |X11_Palette| |Win_Palette|
|----------| |----------| |-----------| |-----------|
| ... | | ... | | ... | | ... |
---------- ---------- ----------- -----------
The big thing about abstract factories is the client only deals with the
abstract classes Window and Palette, only the choice of the factory will
influence the type of the object created. Each concrete factory will
create and setup the objects for the host windowing system without.
04 - Builder
The Builder Pattern splits the construction part of complex objects and the representation of these objects. Thus, a single construction process can create objects having different representation.
The Builder object is a member of the Client class.
You are the editor of a worldfamous popular diskmag (let's say it starts with
an "H" and ends with "ugi") and you want to allows users to save the contents
of the articles in several formats. The goal is to use the same interface to
save the text than to display it on screen.
------------ builder -------------- |Text_Loader |<>-------------->|Text_Converter| |------------| |--------------| |parse_text()| |conv_char(c) | ------------ |conv_font(fnt)| |conv_par() | -------------- | /_\ +-----------------------------------------+ | | | --------------- ---------------- ----------------- |ASCII_Converter| | HTML_Converter | |Screen_Converter | |---------------| |----------------| |-----------------| +-|char*get_text()| +-|char *get_page()| +-|pix *get_pixels()| | --------------- | ---------------- | ----------------- | | | | ------------- | -------------- | -------------- +->/ ASCII text / +->/HTML document/ +->/Screen buffer/ ------------- -------------- --------------The code of the parse_text method should like this:
while ( tok = get_next_token() ) {
switch ( tok.type() ) {
tok.CHAR:
// print char
builder->conv_char( tok.get_char() );
break;
tok.FONT:
// change font
builder->conv_font( tok.get_font() );
break;
tok.PAR:
// paragraph mark
build->conv_par();
break;
// ...
}
}
As you may have notice, there are no "Product" superclass. All the produced
objects are different whatever they represent the same thing.
With this pattern, it's piece of cake to add another converter without having to change the whole conversion system and the core parsing routines.
05 - Singleton
A Singleton is a class for which you can only have ONE AND ONLY ONE instance. In a plain demomaker point of view, such objects includes device drivers or custom memory managers for instance. The main challenge in the design of the Singleton Pattern is that it must be possible to extend (i.e. to inherit from) the class.
To implement a Singleton in C++, we'll use several tricks we'll detail later.
Let's check out the code:
// Singleton.h
class Singleton {
static Singleton *unique_instance;
protected:
Singleton();
public:
static Singleton *get_instance();
};
// Singleton.cc
// Init the static data member to null
Singleton *Singleton::unique_instance = 0;
// Returns the unique instance of the object
Singleton *Singleton::get_instance()
{
if ( unique_instance == 0 )
unique_instance = new unique_instance;
return unique_instance;
}
By protecting the default constructor, we prevent the Singleton from being
instanciated by an external fellow. Only subclasses will be able to invoke
that constructor. As you may have noticed, the Singleton is a self-constructed
object. The Singleton will be constructed only if needed.
I personally used the Singleton pattern in a Network client written in Java. I had defined a Socket Proxy class which was a singleton so every piece of code that needed to read or write from the socket, just ask its own reference to the single object. It's far better to use this way rather than passing a reference to the single object to all client classes. It's absurd to have a Socket argument in the constructor of a dialog box!
I won't explain here how to subclass a singleton since it's quite a difficult operation but if anyone is interested in Design Patterns, I may write more stuff for the next issue of Hugi!
Other Creational Patterns
We've studied several useful Creational Patterns but there are others, like Prototype (the class of an object). I actually just hope I'll have a little more spare to make further investigation on the web and in books to find more Patterns!