Introduction
Factories are useful. Inheritance is useful. Trying to automatically register classes to more than one factory when the classes are derived from a common interface presented some difficulties.
Background
I have a business logic layer acting as a bridge between a web service and database. Part of the responsibility of this logic is to process various incoming orders and perform relevant operations on the data via an ORM tool. The classes used to define these operations are derived from a common interface, but are themselves extended according to the needs of the business.
I needed a way to implement factories to generate these classes, avoiding complicated or rigid logic; making it easy to extend while maintaining Open / Close and DRY principles as much as possible. This is what I came up with.
Using the Code
Each factory should return a class implementing an interface. The interface should derive from an IFactoryObject<T> interface which will define the methods required by the factory.
public interface IOrder : IFactoryObject<IOrder>
{
void OrderMethod1();
void OrderMethod2();
}
public interface IFactoryObject<T>
{
void Register();
T Create(object[] args);
}
Each factory is created as a singleton, deriving from a factory abstract. The abstract requires the factory type, the interface and the create argument as type parameters.
public abstract class FactoryBase<T, Obj, Id>
where T : FactoryBase<T, Obj, Id>, new()
where Obj : IFactoryObject<Obj>
{
When first accessed, the factory will Initialize itself, using reflection to generate a list of implementing classes by calling the Register method of theIFactoryObject interface. Each instance will register itself using the identifier as a key. I've used enums but you could substitute any suitable type.
The factory Create method takes the identifier as an argument and can also be called with other parameters according to need. The registered instance is instantiated and initialized on the first run, and the IFactoryObject.Create method is called to instantiate and return your object.
private static T _instance;
protected Dictionary<Id, Obj> lookup;
private static void CreateInstance()
{
_instance = new T();
_instance.Initialize();
}
protected void Initialize()
{
Type t = typeof(Obj);
if (!t.IsInterface) return;
lookup = new Dictionary<Id, Obj>();
var assembly = Assembly.GetExecutingAssembly();
foreach (var type in assembly.GetTypes())
{
if (!type.IsClass || !t.IsAssignableFrom(type)) continue;
var inst = System.Activator.CreateInstance(type) as IFactoryObject<Obj>;
inst.Register();
}
}
public static void Register(Id identifier, Obj factoryobject)
{
if (_instance == null) CreateInstance();
_instance.RegisterClass(identifier, factoryobject);
}
protected virtual void RegisterClass(Id identifier, Obj factoryobject)
{
if (lookup.ContainsKey(identifier)) return;
lookup.Add(identifier, factoryobject);
}
public static Obj Create(Id identifier, params object[] args)
{
if (_instance == null) CreateInstance();
return _instance.Generate(identifier, args);
}
protected virtual Obj Generate(Id identifier, object[] args)
{
if (!lookup.ContainsKey(identifier)) throw new ArgumentException("Type lookup failed.");
return lookup[identifier].Create(args);
}
The factories themselves are very simple and only define the interface and argument types.
public class OrderFactory : BaseFactory<OrderFactory, IOrder, OrderType>
{
}
public class DerivedOrderFactory : BaseFactory<DerivedOrderFactory, IOrder, OrderType>
{
}
The IFactoryObject implementation should register the object with the required factory and provide a method of creating concrete objects.
public class OrderA : IOrder
{
public virtual void Register()
{
OrderFactory.Register(OrderType.OrderTypeA, this);
}
public virtual IOrder Create(object[] args)
{
return new OrderA();
}
public virtual void OrderMethod1()
{
}
public virtual void OrderMethod2()
{
}
}
public class DerivedOrderA : OrderA
{
public override void Register()
{
DerivedOrderFactory.Register(OrderType.OrderTypeA, this);
}
public override IOrder Create(object[] args)
{
return new DerivedOrderA();
}
public override void OrderMethod1()
{
}
}
Points of Interest
The factories are singletons, but the private constructor and static instance are in the abstract class so they will have a public constructor. However, the Register and Create methods are static so cannot be called from a concrete factory.
Finally, this pattern is not restricted to returning interfaces. With minimal work, the factory abstract could be extended to handle any class which implements IFactoryObject<T>, and does not necessarily have to return an interface.