Abstract Factory Manager






3.64/5 (8 votes)
Sep 17, 2005
4 min read

39850

133
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
- Creational Pattern - Abstract Factory by Ashish Jaiman.
- Abstract Factory UML model and examples.
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:
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:
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.
// 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
.
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
.
[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.
[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:
[Creates( new type [] { typeof(Hops), typeof(HopsControl) } )]
public class HopsFactory : IBeerFactory
or
[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.
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.
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.
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.
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() );
}
}
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!