Abstract Factory Design Pattern


Question

I'm working on an internal project for my company, and part of the project is to be able to parse various "Tasks" from an XML file into a collection of tasks to be ran later.

Because each type of Task has a multitude of different associated fields, I decided it would be best to represent each type of Task with a seperate class.

To do this, I constructed an abstract base class:

public abstract class Task
{
    public enum TaskType
    {
        // Types of Tasks
    }   

    public abstract TaskType Type
    {
        get;
    }   

    public abstract LoadFromXml(XmlElement task);
    public abstract XmlElement CreateXml(XmlDocument currentDoc);
}

Each task inherited from this base class, and included the code necessary to create itself from the passed in XmlElement, as well as serialize itself back out to an XmlElement.

A basic example:

public class MergeTask : Task
{

    public override TaskType Type
    {
        get { return TaskType.Merge; }
    }   

    // Lots of Properties / Methods for this Task

    public MergeTask (XmlElement elem)
    {
        this.LoadFromXml(elem);
    }

    public override LoadFromXml(XmlElement task)
    {
        // Populates this Task from the Xml.
    }

    public override XmlElement CreateXml(XmlDocument currentDoc)
    {
        // Serializes this class back to xml.
    }
}

The parser would then use code similar to this to create a task collection:

XmlNode taskNode = parent.SelectNode("tasks");

TaskFactory tf = new TaskFactory();

foreach (XmlNode task in taskNode.ChildNodes)
{
    // Since XmlComments etc will show up
    if (task is XmlElement)
    {
        tasks.Add(tf.CreateTask(task as XmlElement));
    }
}

All of this works wonderfully, and allows me to pass tasks around using the base class, while retaining the structure of having individual classes for each task.

However, I am not happy with my code for TaskFactory.CreateTask. This method accepts an XmlElement, and then returns an instance of the appropriate Task class:

public Task CreateTask(XmlElement elem)
{
    if (elem != null)
    {
        switch(elem.Name)
        {
            case "merge":
                return new MergeTask(elem);
            default:
                throw new ArgumentException("Invalid Task");
        }
    }
}

Because I have to parse the XMLElement, I'm using a huge (10-15 cases in the real code) switch to pick which child class to instantiate. I'm hoping there is some sort of polymorphic trick I can do here to clean up this method.

Any advice?

1
21
9/16/2008 2:37:22 PM

Accepted Answer

I use reflection to do this. You can make a factory that basically expands without you having to add any extra code.

make sure you have "using System.Reflection", place the following code in your instantiation method.

public Task CreateTask(XmlElement elem)
{
    if (elem != null)
    { 
        try
        {
          Assembly a = typeof(Task).Assembly
          string type = string.Format("{0}.{1}Task",typeof(Task).Namespace,elem.Name);

          //this is only here, so that if that type doesn't exist, this method
          //throws an exception
          Type t = a.GetType(type, true, true);

          return a.CreateInstance(type, true) as Task;
        }
        catch(System.Exception)
        {
          throw new ArgumentException("Invalid Task");
        }
    }
}

Another observation, is that you can make this method, a static and hang it off of the Task class, so that you don't have to new up the TaskFactory, and also you get to save yourself a moving piece to maintain.

12
8/26/2008 2:45:00 AM

Create a "Prototype" instanace of each class and put them in a hashtable inside the factory , with the string you expect in the XML as the key.

so CreateTask just finds the right Prototype object, by get() ing from the hashtable.

then call LoadFromXML on it.

you have to pre-load the classes into the hashtable,

If you want it more automatic...

You can make the classes "self-registering" by calling a static register method on the factory.

Put calls to register ( with constructors) in the static blocks on the Task subclasses. Then all you need to do is "mention" the classes to get the static blocks run.

A static array of Task subclasses would then suffice to "mention" them. Or use reflection to mention the classes.


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon