Click here to Skip to main content
15,885,757 members
Articles / Programming Languages / C#
Article

Abstract Factory Manager

Rate me:
Please Sign up or sign in to vote.
3.64/5 (8 votes)
17 Sep 20054 min read 39.6K   133   48   3
This article details how to create a reusable managing class for the abstract factory pattern.

Introduction

The abstract factory provides an interface for creating families of related dependent objects without specifying their concrete classes. Suppose you have a great number of concrete factories, and the user is given the ability to choose which type of concrete product they would like to use at runtime. You'll need an efficient way to determine the correct factory to use, in order to create the desired and related types. This article shows how to develop a small utility to manage the creation of objects using the abstract factory pattern and custom attributes.

Background

Context

The reason for developing this code was to eliminate or at least significantly reduce the amount of coding to be done whenever a new concrete factory class is added to a project. I also needed a way so users could choose the type they wished to create without knowing how the specific object is created. How do we determine which factory creates the type the user has chosen? Of course! A giant switch statement... No, that just won't do, that would just be too tedious and require an additional case statement for every new factory. What we really need is a custom attribute for factories which tell us the types they can create. Yeah, that would be nifty.

The Interfaces

Consider a system composed of data objects that have corresponding controls. An abstract factory for such a system might look like so:

C#
public interface IBeerFactory
{
    IBeerIngredient    CreateIngredient();
    IBeerControl    CreateControl();
}

So now we know that all types implementing IBeerFactory must create both Controls and Ingredients, we had better define these as well:

C#
public interface IBeerIngredient
{
    string Name { get;set; }
    double Amount { get;set; }
    string Description { get;set; }
}

public interface IBeerControl
{
    void UpdateControl( IBeerIngredient ingr );
    void UpdateIngredient( IBeerIngredient ingr );
}

The Code

Of course, we will need to go on and develop some concrete factories, ingredients, and controls. For instance, there could be a set of classes for Hops, Barley, and Yeast. To keep things short, I'll show the code for the Hops ingredient, control and factory. Then we'll be ready to get to work on the Factory Manager.

C#
// Data Object that stores info about the deliciously 
// bitter hops plant.
public class Hops : IBeerIngredient
{
    private string mName;
    private string mDesc;
    private double mAmt;

    internal Hops()
    {
        mName = ""; 
        mDesc = "";
        mAmt = 0.0;
    }

    public string Name
    {
        get
        {
            return mName;
        }
        set
        {
            mName = value;
        }
    }

    public double Amount
    {
        get
        {
            return mAmt;
        }
        set
        {
            mAmt = value;
        }
    }

    public string Description
    {
        get
        {
            return mDesc;
        }
        set
        {
            mDesc = value;
        }
    }
}

// The user control so users can control the hops object.
// Seems a little redundant?!
public class HopsControl : IBeerControl, UserControl
{
    private TextBox txtName;
    private TextBox txtDesc;
    private TextBox txtAmt;
    
    internal HopsControl()
    {
        Initialize();
    }

    private void Initialize()
    {
        txtName = new TextBox();
        txtName.Parent = this;

        txtDesc = new TextBox();
        txtDesc.Parent = this;

        txtAmt = new TextBox();
        txtAmt.Parent = this;
    }

    public void UpdateControl(IBeerIngredient ingr)
    {
        // Make sure the Ingredient is indeed hops
        Hops hops = ingr as Hops;
        if( hops != null )
        {
            // Set the controls
            txtName.Text = hops.Name;
            txtDesc.Text = hops.Description;
            txtAmt.Text = hops.Amount;
        }
    }

    public void UpdateIngredient(IBeerIngredient ingr)
    {
        // Once again make sure the Ingredient is hops
        Hops hops = ingr as Hops;
        if( hops != null )
        {
            // Set the ingredient
            hops.Name = txtName.Text;
            hops.Description = txtDesc.Text;
            hops.Amount = Convert.ToDouble( txtAmt.Text );
        }
    }
}

// A simple little class to return new objects
public class HopsFactory : IBeerFactory
{
    public IBeerIngredient CreateIngredient()
    {
        return new Hops();
    }

    public IBeerControl CreateControl()
    {
        return new HopsControl();
    }
}

Okay, that was simple enough. So now we will turn to Custom Attributes so our factory manager will be able to determine what can be created by the concrete factories. We'll definitely need something generic because I too am lazy and never want to reproduce this code again. What came to mind was an Attribute that stores all the types a factory can create. Let's call it CreatesAttribute. Like any custom attribute, it must inherit from System.Attribute.

C#
public class CreatesAttribute : System.Attribute
{
    private Type [] mCreatableTypes;
}

Custom attributes also need to tell the compiler how it can be used so the correct meta data will be emitted into the created assembly. This is done by using the AttributeUsageAttribute.

C#
[AttributeUsage( AttributeTargets.Class, Inherited=false, AllowMultiple=true )]
public class CreatesAttribute : System.Attribute
{
    private Type [] mCreatableTypes;
}

So our custom attribute can be placed on classes, it will not be inherited by any sub classes, and we will allow multiple CreatesAttribute to be specified for the factories. Sounds good? ...Cool!

No doubt we'll need some constructors and a way to access the creatable types. How about creating two constructors, one that will take an array of types and another taking a single type. As for accessing, let's just make a simple little get property.

C#
[AttributeUsage( AttributeTargets.Class, Inherited=false, AllowMultiple=true )]
public class CreatesAttribute : System.Attribute
{
    private Type [] mCreatableTypes;
    
    // The Ctor taking a single type
    public CreatesAttribute( Type type )
    {
        mCreatableTypes = new Type [] { type };
    }
    
    // The Ctor taking a list of types
    public CreatesAttribute( Type [] types )
    {
        mCreatableTypes = types;
    }
    
    // The way to access the creatable types.
    public Type [] CreatableTypes
    {
        get
        {
            return mCreatableTypes;
        }
    }
}

Don't forget to go and mark the concrete factories with our new custom attribute. Use either of the following ways:

C#
[Creates( new type [] { typeof(Hops), typeof(HopsControl) } )]
public class HopsFactory : IBeerFactory

or

C#
[Creates( typeof(Hops) )]
[Creates( typeof(HopsControl) )]
public class HopsFactory : IBeerFactory

Okay, okay, I know I've been yacking on long enough so let's get to the good stuff. The FactoryManager is the class that will manage the retrieval of the concrete classes implementing the IBeerFactory interface. Yet that still isn't good enough. Let's design it so that it will manage any possible factory interface that we'll ever create.

C#
using System;
using System.Collections;
using System.Reflection;

public sealed class FactoryManager
{
    private Type mFactoryInterface;  // The interface to manage
    private SortedList mFactories;   // The factories
    
    public FactoryManager( Type factoryInterface )
    {
        mFactoryInterface = factoryInterface;
        mFactories = new SortedList();
    }

You may be wondering how we are going to retrieve all of our concrete factories and place them in the sorted list. Well, let's reflect on that a little bit (pun intended, ha ha ha, very lame). Because we may not know where all the factories exist, let's give that choice to the developer by making them load the appropriate assembly.

C#
public void LoadFactoriesFromAssembly( Assembly assembly )
{
    Type [] allTypes = assembly.GetTypes();

   //Search through all the types in the assembly
    foreach( Type type in allTypes )
    {
        Type intrface = type.GetInterface(mFactoryInterface.FullName);
        // Look for classes implementing the factory interface
        if( intrface != null )
        {
            // Get an array containing the CreatesAttributes
            object [] attribs =
              type.GetCustomAttributes( typeof(CreatesAttribute), false );
            object factory;

            // If any CreatesAttributes were defined
            if( attribs.Length != 0 )
            {
                // Create an instance of the factory
                factory = assembly.CreateInstance( type.FullName );

                // Loop through each custom attribute
                // because multiple Creates
                // may have been defined.
                foreach( CreatesAttribute createsAttrib in attribs )
                {
                    // Get an array of creatable types.
                    Type [] createsTypes = createsAttrib.CreatableTypes;

                    // Store the pair in the sorted list
                    // by using the creatable Types
                    // full name as the key and the instance
                    // of the factory as the value.
                    foreach( Type creatable in createsTypes )
                    {
                        mFactories[creatable.FullName] = factory;
                    }
                }
            }
        }
    }
}

Wow! That's pretty cool isn't it? Well I think so anyway. Of course we better finish up with a couple functions that allow us to get the factories.

C#
    public object GetFactory( string fullname )
    {
        // Make sure a factory exists that can create the desired type
        if( mFactories.ContainsKey( fullname )
            return mFactories[fullname];
        
        return null;
    }
    
    public object GetFactory( Type type )
    {
        return GetFactory( type.FullName );
    }
}

Finally, I'll show a simple example of the FactoryManager at work.

C#
public class Example
{
    public static void Main()
    {
        FactoryManager mgr = new FactoryManager( typeof(IBeerFactory) );

        // Just load this assembly
        Console.WriteLine( "Loading Assembly: AbstractFactoryManager");
        mgr.LoadFactoriesFromAssembly( mgr.GetType().Assembly );

        // Get a factory using the type of hops
        Console.WriteLine( "Retreiving a HopsFactory");
        IBeerFactory factory = (IBeerFactory)mgr.GetFactory( typeof(Hops) );
        Console.WriteLine( "IBeerFactory factory is of type {0}\n", 
                                               factory.GetType() );

        // Create an ingredient and a control
        Console.WriteLine( "Creating an Ingredient.");
        IBeerIngredient ingr = factory.CreateIngredient();
        Console.WriteLine( "Creating a Control.");
        IBeerControl ingrCtrl = factory.CreateControl();

        // Let's display the types to see if it worked.
        Console.WriteLine( "IBeerIngredient ingr is of type {0}", 
                                                ingr.GetType() );
        Console.WriteLine( "IBeerControl ingrCtrl is of type {0}\n\n", 
                                                 ingrCtrl.GetType() );


        // Get a factory using the fully qualified name of a yeast control
        Console.WriteLine( "Retrieving a YeastFactory");
        factory = (IBeerFactory)
           mgr.GetFactory( "AbstractFactoryManager.YeastControl" );
        Console.WriteLine( "IBeerFactory factory is of type {0}\n", 
                                               factory.GetType() );

        // Create an ingredient and a control
        Console.WriteLine( "Creating an Ingredient.");
        ingr = factory.CreateIngredient();
        Console.WriteLine( "Creating a Control.");
        ingrCtrl = factory.CreateControl();

        // Let's display the types to see if it worked.
        Console.WriteLine( "IBeerIngredient ingr is of type {0}", 
                                                ingr.GetType() );
        Console.WriteLine( "IBeerControl ingrCtrl is of type {0}\n", 
                                               ingrCtrl.GetType() );
    }
}

Abstract Factory Manager Example Output

Points of Interest

The FactoryManager class can be very helpful, and can be used in any project where there is a need for the abstract factory design pattern. I hope this will help a few developers out there save some time. Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Engineer Click Commerce.
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMissing picture Pin
fwsouthern17-Sep-05 15:41
fwsouthern17-Sep-05 15:41 
GeneralRe: Missing picture Pin
The_Mega_ZZTer17-Sep-05 17:06
The_Mega_ZZTer17-Sep-05 17:06 
GeneralRe: Missing picture Pin
Jake Bruun18-Sep-05 5:03
Jake Bruun18-Sep-05 5:03 

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.