C sharp:GenericFactory

From GPWiki
Jump to: navigation, search
40px-broken-icon.png   This page has broken markup.
If you fix this problem, please remove this banner.


Note: The correct name of this page is C#:Generic Factory; however, due to technical restrictions it is represented as C_sharp:Generic Factory instead.

Sometimes you want to instantiate a class from an input variable at runtime, but at compile time you don't know which class you will be instantiating. For example: reading message packets off of a network connection or when reading saved objects in a custom file format. A class factory is a useful design pattern to solve this common problem.

In .NET 2.0 you can use generics with constraints to create a Factory that can be used with any type of class you want to instantiate. In this way, you will only ever have to code a single Factory class. To do this we create a generic class with two type parameters TKey and TBase. TKey is the type of key to use when generating the objects. TBase is the base class of the classes that will be instantiated by this Factory. Depending on what classes you want to use you can use different types TKey and TBase without having to rewrite the factory class.

using System; using System.Collections.Generic;

namespace GPWiki {

   /// <summary>
   /// Factory design pattern for instanciating classes based on a key. 
   /// </summary>
   /// <typeparam name="TKey">The type of the key.</typeparam>
   /// <typeparam name="TBase">The type of the base class of the classes being instanciated.</typeparam>
   public class Factory<TKey, TBase>
   {
       private delegate TBase CreateHandler();
       Dictionary<TKey, CreateHandler> _Products = new Dictionary<TKey, CreateHandler>();
       /// <summary>
       /// Registers a type with the factory.
       /// </summary>
       /// <typeparam name="T">The type to register for production.</typeparam>
       /// <param name="key">The key associated with the type.</param>
       public void Register<T>(TKey key) where T : TBase, new()
       {
           _Products.Add(key, Constructor<T>);
       }
       /// <summary>
       /// Creates a new instance of a type.
       /// </summary>
       /// <param name="key">The key associated with the type</param>
       /// <returns>A new instance of TBase.</returns>
       public TBase Produce(TKey key)
       {
           return _Products[key]();
       }
       private TBase Constructor<T>() where T : TBase, new()
       {
           return new T();
       }
   }

}

The Constructor<T> method creates a delegate that creates a class of type T. The generic constraint "where" ensure that T inherits from TBase and that T has a parameter-less constructor.

The Register<T> method registers a class you want to be able to instantiate, and registers the key you want to use to instantiate this class. It also places the same constraints on T as the constructor method.

The generic dictionary maintains the key/constructor associations.

When you want to instantiate a class from a key value, you just call use the Produce method.

Usage

For example, say you have to following classes.

abstract class Car {

   public abstract int GetSpeed();

} class Ferrari : Car {

   public override int GetSpeed()
   {
       return 200;
   }

} class Nissan : Car {

   public override int GetSpeed()
   {
       return 100;
   }

}

If you want to use strings to instantiate Car child classes you create the factory with TKey = string and TBase = Car.

Factory<string, Car> carFactory = new Factory<string, Car>();

Then you register the classes you want carFactory to instantiate.

carFactory.Register<Ferrari>("ferrari"); carFactory.Register<Nissan>("nissan");

When you want to use the factory you simply call Produce and pass it the key.

Car car = carFactory.Produce("nissan"); Console.WriteLine(car.GetSpeed()); //Outputs 100

Added Functionality

Sometimes it is useful to be able to access the key from the instantied class. To do this you just need to add this simple base class.

namespace GPWiki {

   /// <summary>
   /// <![CDATA[Base class for use with a Factory<TKey,TBase> when the instantiated products need to know their own key.]]>
   /// </summary>
   public abstract class Product<TKey>
   {
       private TKey _Key;
       /// <summary>
       /// Gets the product's key.
       /// </summary>
       public TKey Key
       {
           get { return _Key; }
       }
       /// <summary>
       /// Creates a new instance of the product.
       /// </summary>
       /// <param name="key">The product's key.</param>
       public Product(TKey key)
       {
           _Key = key;
       }
   }

}

Then it just a matter ensuring that the classes are always instantiated with the key from their parameter-less constructor.

abstract class Car : Product<string> {

   public Car(string name)
       : base(name)
   {
   }
   public abstract int GetSpeed();

} class Ferrari : Car {

   public Ferrari()
       : base("ferrari")
   {
   }
   public override int GetSpeed()
   {
       return 200;
   }

} class Nissan : Car {

   public Nissan()
       : base("nissan")
   {
   }
   public override int GetSpeed()
   {
       return 100;
   }

}

Once you have done this you can also add more functionality to the factory itself. You can derive a class from Factory<string,Car> and create a new, easier to use, function for registering classes that inherit from Car.

class CarFactory : Factory<string, Car> {

   public void Register<T>() where T : Car, new()
   {
       this.Register<T>(new T().Key);
   }

}

Using the new factory is simple, just create a CarFactory object, register the types you want it to instantiate, and call the Produce method.

CarFactory factory = new CarFactory(); factory.Register<Ferrari>(); factory.Register<Nissan>(); Car car = factory.Produce("ferrari"); Console.WriteLine(car.GetSpeed()); //Outputs 200

Conclusion

Using a generic factory is probably overkill for many simple situations. However, if you have a large number of classes you want to instantiate in a type-safe and extensible way, a generic factory can prove to be extremely useful and well worth the initial time it takes setup well. You could just use a switch statement, but then you lose the ability to add classes after compilation and you must change the switch statement with each class you want to be able to instantiate. Factories will likely make your application code much cleaner and maintainable.