Introduction
The target of this article is to show another factory implementation using delegates. I've searched CodeProject for a similar implementation and though some are close, they're not quite the same. So I've decided to post this factory which I use in my current project.
This article does not aim at explaining the whole concept of factory or why you would use that particular design pattern.
Table of Contents
The goals in writing this factory are as follows:
- Dynamic factory - It will be possible in the future to use the same factory for new types of objects without opening the factory's code.
- Creation responsibility - The factory should not know how to create the objects.
- Ease of use.
Using delegates will help us achieve goals 1 and 2, it might set us on a collision course with goal 3 but we'll see.
The declaration of the delegate looks like this:
public delegate AObject ObjectCreator(params object [] list);
This delegate will help us obscure the way objects are created from the factory and help us register delegates of this type on the factory.
The factory will create our first usable object in our tree, so that we don't have to downcast straight away (only if it's really needed).
The base class is abstract
and defines one variable member and one abstract
function:
public abstract class AObject
{
#region Override
public abstract void Print();
#endregion
#region protected
protected Int32 m_nType;
#endregion
}
The derived classes look like this:
public class Class1 : AObject
{
#region Constants
public const Int32 ClassType = 1;
#endregion
#region C'tor
internal Class1()
{
this.m_nType = ClassType;
}
#endregion
#region Overrides
public override void Print()
{
String msg = String.Format("Class: {0, 20} Value: {1, 10}",
ToString(), m_nType*67);
Console.WriteLine(msg);
}
#endregion
#region Static
public static AObject ObjectCreator(params object[] list)
{
return new Class1();
}
#endregion
}
As you can see, we implemented a Print
function for the derived that does something (hopefully something different than other derived) and we've added a static
function that creates a new object of Class1
.
The static
function ObjectCreator
keeps the responsibility of creating objects within the class.
The factory
class maps the classes' static
functions, with the help of a delegate to the type that we wish to create.
The map is done through a hashtable with the type of the object being the key and the delegate that encapsulates a static
function as the value
.
The factory:
public class ObjectFactory
{
#region Static
public static bool RegisterHandler(Int32 type, ObjectCreator creator)
{
bool res = false;
try
{
if (m_handlers[type] != null)
return false;
m_handlers[type] = creator;
res = true;
}
catch(Exception ex)
{
Console.WriteLine("Can't register handler - "+ex.Message);
}
return res;
}
public static bool UnregisterHandler(Int32 type)
{
bool res = true;
try
{
if (m_handlers[type] == null)
return res;
m_handlers[type] = null;
GC.Collect();
}
catch(Exception ex)
{
Console.WriteLine("Can't unregister handler - "+ex.Message);
res = false;
}
return res;
}
public static AObject CreateObject(Int32 type, params object [] list)
{
AObject aobject = null;
try
{
ObjectCreator creator = (ObjectCreator)m_handlers[type];
if (creator != null)
aobject = creator(list);
}
catch(Exception ex)
{
Console.WriteLine("Can't get object from handler - "+ex.Message);
}
return aobject;
}
#endregion
#region Protected
protected static Hashtable m_handlers = new Hashtable();
#endregion
}
Notice that the factory
has register
and unregister
functions, later on, we'll see how to use them.
RegisterHandler
- The register
function that takes a delegate and inserts it to the hashtable with the class identifier as the key. UnregisterHandler
- The unregister
function takes the type
and removes whatever delegate was there as the value
. CreateObject
- The function that creates objects according to their type
(key). This function extracts a delegate from the hashtable from position type
and invokes the delegate (calls the static
function of the object that we've registered before).
After comments from numerous esteemed colleagues, I changed the following code a bit, so it's more obvious that the factory doesn't "know" what type it's getting, but rather checks to see if it knows how to create an object with the provided key (type).
static void Main(string[] args)
{
try
{
ObjectFactory.RegisterHandler(Class1.ClassType,
new ObjectCreator(Class1.ObjectCreator));
ObjectFactory.RegisterHandler(Class2.ClassType,
new ObjectCreator(Class2.ObjectCreator));
ObjectFactory.RegisterHandler(Class3.ClassType,
new ObjectCreator(Class3.ObjectCreator));
AObject aobject = null;
for (int i = 0; i<100; i++)
{
aobject = ObjectFactory.CreateObject(i%3+1, null);
aobject.Print();
}
if (!ObjectFactory.UnregisterHandler(Class1.ClassType))
Console.WriteLine("Really ?!");
aobject = ObjectFactory.CreateObject(Class1.ClassType, null);
if (aobject != null)
aobject.Print();
else
Console.WriteLine("aobject is null");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
Notice the registration process:
We pass a type
to be registered and we create a new delegate passing it the static
function of the type that we're registering. We have to make sure that type
s are registered before we can use the factory to create objects, so the registration process should be as soon as possible in the program.
One more thing to notice is that Class3
(if you've downloaded the code, you may have noticed already) belongs to the same namespace
(and assembly) as Main
and not to the namespace
(and assembly) that the rest of the classes belong to. This means that we have achieved goal 1 and goal 2.
I leave it up to you to decide if goal 3 was achieved.