Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Generic Abstract Factory

0.00/5 (No votes)
23 Apr 2014 1  
Generic Abstract Factory Design Pattern

Introduction

Abstract Factory is designed to create families of related or dependent objects by using interfaces and classes to defer the actual creation of concrete products in the concrete factory subclasses. If you are not familiar with this design pattern, I suggest you read the Abstract Factory section of the GoF Design Patterns book. Some familiarity with the basic principles of C# Generics, most especially Constraints, would also help.

Background

Abstract Factory is a powerful pattern but has its limitations. I will begin by giving an example of each type of Abstract Factory, then proceed with the discussion and also point out some limitations of the pattern. The next few sections might be too basic for you so if it is then just skip it. The reason for this introduction is because the code implementations for the generic version of this pattern varies a bit more between the different types compared to the non-generic version.

Related Type

Here is an example of a factory that creates families of related objects:

interface ICarFactory
{
    ICarProduct CreateToyota();
    ICarProduct CreateHonda(); 
}

The factory creates different brands of cars and each of them is totally independent from one another. You can drive a Toyota and go places with it without having to own a Honda. In the above code, both functions return the same type of interface. To create another brand of car, you have to modify the interface and add ICarProduct CreatePorshe(); for instance. But since all of them return only one specific interface, using a function parameter to identify the kind of product to be created is recommended. This approach would minimize the number of functions in the factory to a single Create function. This new function is a skeleton for another GoF pattern called "Factory Method" and having this pattern inside an "Abstract Factory" is pretty common:

interface ICarFactory
{
    ICarProduct Create(int productKind);
}

Dependent Type

Here is an example of a factory that creates families of dependent objects:

interface ICarFactory
{
    IEngineProduct CreateEngine();
    IFrameProduct CreateFrame();
}

The factory creates different types of car products. Each product is dependent on the other to create a car. You can't drive a car with a frame but no engine. The factory uses specific interfaces to create a product which is a typical example of an Abstract Factory pattern.

Hybrid

As the name implies, hybrid is a mixture of the two types of this pattern. Here is an example:

interface ICarFactory
{
    IEngineProduct CreateEngine(int engineKind);
    IFrameProduct CreateFrame(int frameKind);
}

If you need to break down the products into sub-categories, use hybrid. And if you want to be more fancy, you can nest an "Abstract Factory" within another "Abstract Factory" instead of using a "Factory Method". Using a more specific interface that returns a more specific product kind offers more flexibility, but is still not flexible enough. Other than modifying the interface's code to create new products, the client is also limited to use only functions exposed by the interface. Limited unless the client downcasts to get to the other functions not exposed by the interface. In the example below, the interface only has an Operate() function and downcasting was used to call a product-specific operation EngineSpecificOperation():

IEngineProduct hybrid = carFactory.CreateEngine(0);
hybrid.Operate();
 
// Compiler error since this function is not in the interface.
//hybrid.EngineSpecificOperation();
 
// Downcast to access product-specific operation.
((Hybrid)hybrid).EngineSpecificOperation();

Casting is a computationally expensive process and its use should be minimized. Also, wrong type casts are not identified at compile time and causes an InvalidCastException at runtime. In the next section, I will show you how you can improve the pattern's flexibility.

Generics

How can we improve the pattern? By adding a touch of Generics! With Generics, we can create flexible factories that support new products without modifying factory code and perform product-specific behavior without downcasting.

Abstract Factory

Below we define a factory interface. It only knows one thing, to build a product. Here the factory can be anything (e.g. car factory, plane factory, etc.) and it can produce anything as well. We can also make a car factory produce other objects that a car factory would not normally make like slippers for instance. It would not make any sense but we can always do it if we like. We can also restrict which objects a factory can produce. More on this next.

interface IFactory<TFactory>
{
    TProduct Build<TProduct>() where TProduct : IProduct<TFactory>, new();
} 

Concrete Factories

Below are concrete implementations of car and plane factories. Here's where we define that a car factory only creates car products via Generic constraints "where TProduct : IProduct<Car>". We also specified the new() constraint because we would like to instantiate the product.

class Car : IFactory<Car>
{
    public TProduct Build<TProduct>() where TProduct : IProduct<Car>, new()
    {
        Console.WriteLine("Creating Car: " + typeof(TProduct));
        return new TProduct();
    }
}

class Plane : IFactory<Plane>
{
    public TProduct Build<TProduct>() where TProduct : IProduct<Plane>, new()
    {
        Console.WriteLine("Creating Plane: " + typeof(TProduct));
        return new TProduct();
    }
}

Abstract Product

Now that we have the necessary factories, we define the product interface:

interface IProduct<TFactory>
{
    void Operate();
}

Concrete Products

Below are the "Concrete Product" classes using Generic Abstract Factory "related" type. Based on what these classes implement, we can tell what type of factory can build these products. Honda and Toyota can be built by a car factory. Boeing can be built by a plane factory. Saab can be built by both car and plane factories.

class Honda : IProduct<Car>
{
    public void Operate()
    {
        Console.WriteLine("Driving Honda.");
    }
 
    public void HondaSpecificOperation()
    {
        Console.WriteLine("Performing Honda-specific operation.");
    }
}
 
class Toyota : IProduct<Car>
{
    public void Operate()
    {
        Console.WriteLine("Driving Toyota.");
    }
 
    public void ToyotaSpecificOperation()
    {
        Console.WriteLine("Performing Toyota-specific operation.");
    }
}

class Boeing : IProduct<Plane>
{
    public void Operate()
    {
        Console.WriteLine("Flying Boeing.");
    }
 
    public void BoeingSpecificOperation()
    {
        Console.WriteLine("Performing Boeing-specific operation.");
    }
}

class Saab : IProduct<Car>, IProduct<Plane>
{
    public void Operate()
    {
        Console.WriteLine("Operating Saab.");
    }
 
    public void SaabSpecificOperation()
    {
        Console.WriteLine("Performing Saab-specific operation.");
    }
}

Below are the "Concrete Product" classes using Generic Abstract Factory "dependent" type. Like the previous type, we can tell what type of factory can build these products based on what these classes implement. Engine can be built by any factory. Frame can be built by a car and plane factory. Rudder can only be built by a plane factory. What sets this implementation apart from the other is the generic type parameter of the class. This parameter allows us to specify the actual product we would like to build. In other words, it's possible for a car factory to create a plane frame and vice-versa. Of course it still depends on how the concrete factory's constraint is configured. Keep reading and you'll see some examples later.

class Engine<TFactory> : IProduct<TFactory>
{
    public void Operate()
    {
        Console.WriteLine("Operating Engine.");
    }
 
    public void EngineSpecificOperation()
    {
        Console.WriteLine("Performing Engine-specific operation.");
    }
}

class Frame<TFactory> : IProduct<Car>, IProduct<Plane>
{
    public void Operate()
    {
        Console.WriteLine("Operating Frame.");
    }
 
    public void FrameSpecificOperation()
    {
        Console.WriteLine("Performing Frame-specific operation.");
    }
}

class Rudder : IProduct<Plane>
{
    public void Operate()
    {
        Console.WriteLine("Operating Rudder.");
    }
 
    public void RudderSpecificOperation()
    {
        Console.WriteLine("Performing Rudder-specific operation.");
    }
}

Client

Now let's define the "Client". The client creates a concrete implementation of the abstract factory which is eventually used to create the concrete products.

class Factory<TFactory> where TFactory : IFactory<TFactory>, new()
{
    public TProduct Create<TProduct>() where TProduct : IProduct<TFactory>, new()
    {
        return new TFactory().Build<TProduct>();
    }
}

Relative Client Calls

Below are some sample client calls for "relative" type.

Here we still can't get to the product-specific operations not exposed by the interface. In the next examples, I'll show you how you can.

Factory<Car> carFactory = new Factory<Car>();
 
IProduct<Car> carProduct = carFactory.Create<Toyota>();
carProduct.Operate();

// Not in interface.
//carProduct.ToyotaSpecificOperation();

Just playing around here. Below shows that we can't mix and match products and any attempts to do so will be caught by the compiler.

// Cannot implicitly convert "Honda" to "IProduct<Plane>".
//IProduct<Plane> planeProduct = carFactory.Create<Honda>();

// Cannot implicitly convert "Honda" to "Toyota".
//Toyota toyota = carFactory.Create<Honda>();
 
Factory<Plane> planeFactory = new Factory<Plane>();
// Cannot implicitly convert type "Boeing" to "ICarProduct".
//IProduct<Car> bad = planeFactory.Create<Boeing>();
 
// "Boeing" must be convertible to "IProduct<Car>".
//carFactory.Create<Boeing>();

// "Honda" must be convertible to "IProduct<Plane>".
//planeFactory.Create<Honda>();

// "Toyota" must be convertible to "IProduct<Plane>".
//planeFactory.Create<Toyota>();

Below shows that we can now get to the product-specific operations not exposed by the interface without casting. If this was a Non-generic Abstract Factory, we would have to do something like this:

Honda honda = (Honda)carFactory.Create(0);

If what gets created does not happen to be a Honda then we get an InvalidCastException. Just right above, we see that the compiler catches mismatches.

Honda honda = carFactory.Create<Honda>();
honda.Operate();
honda.HondaSpecificOperation();

Below shows that both a car and plane factory can create a Saab.

Saab saab1 = carFactory.Create<Saab>();
saab1.Operate();
saab1.SaabSpecificOperation();
 
Saab saab2 = planeFactory.Create<Saab>();
saab2.Operate();
saab2.SaabSpecificOperation();

Dependent Client Calls

Here are some sample "dependent" type client calls.

Factory<Car> carFactory = new Factory<Car>();
IProduct<Car> carProduct = carFactory.Create<Engine<Car>>();
carProduct.Operate();
 
Factory<Plane> planeFactory = new Factory<Plane>();
IProduct<Plane> rudderProduct = planeFactory.Create<Rudder>();
rudderProduct.Operate();

Sample mismatches that are caught by the compiler.

// Cannot implicitly convert type "Engine<Car>" to "IProduct<Plane>".
//IProduct<Plane> planeProduct = carFactory.Create<Engine<Car>>();

// Cannot implicitly convert "Engine<Car>" to "Frame<Car>".
//Frame<Car> carFrame = carFactory.Create<Engine<Car>>();
 
// "Rudder" must be convertible to "IProduct<Car>".
//carFactory.Create<Rudder>();
 
// "Engine<Plane>" must be convertible to "IProduct<Car>".
//carFactory.Create<Engine<Plane>>(); 

// Cannot implicitly convert type "Rudder" to "IProduct<Car>".
//IProduct<Car> bad = planeFactory.Create<Rudder>();

// "Engine<Car>" must be convertible to "IProduct<Plane>".
//planeFactory.Create<Engine<Car>>();

Example of how we get to product-specific operations not exposed by the interface.

Frame<Car> carFrame = carFactory.Create<Frame<Car>>();
carFrame.Operate();
carFrame.FrameSpecificOperation();

Example of a frame created by the other factory. Here we specify the actual product we would like to build via generic type parameter. A bit scary one might say if you think about a plane with a frame created by a car factory.

carFactory.Create<Frame<Plane>>();
planeFactory.Create<Frame<Car>>();

Conclusion

With great flexibility comes great complexity. Does it sound obvious that I recently saw Spiderman? But really, that's the tradeoff. Generics adds more flexibility to the Abstract Factory pattern but it also adds complexity to it. Well if you think about it the complexity is really not that bad. Once you have established the foundation and the framework is in place, the rest should be easy.

History

  • 25 October, 2007 - Posted article
  • 22 April, 2014 - Minor article updates

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