Monday, 16 April 2012

Abstract Factory

Abstract Factory is a classic creational design pattern. The aim of the following post is to describe in which cases and how this pattern may be applied to give us certain advantages.  Like many other creational patterns this pattern gives us benefits through defining the way classes are instantiated.

Target class systems

The Abstract Factory pattern deals with classes which make up a system representable in the following form:

image

Example:

Our software running on an automated delivery machine needs to create envelope and stamp objects.  The machine works with envelopes and stamps from different vendors (in this example DHL and UPS) However, a DHL envelope may only be used with DHL stamps and the same is true for UPS envelopes and stamps. Thus, we have two sets of classes one for each delivery service provider.

image

 

Problem formulated

A client method needs to instantiate classes from different families. All the classes to be instantiated in the method will belong to the same set of classes.  However, the method should know nothing about which set of classes it is intended to be dealing with. 

Example:

For DHL and UPS we have the same logic that guides a user through the process of sticking a stamp on a letter.  The business logic needs to be independent of the service provider, yet it should never allow for mixing of products by different companies.

Solution approaches

Approach 1: we only need a way to instantiate classes; the logic of processing them will be entirely implemented in the client code.

We create factory classes one for each set of classes.  Each factory class implements a factory interface. The interface declares instantiation methods one for each class family.  We make product class interfaces one for each class family. Instantiation products will be returned from factories through these interfaces. Factories will be provided to the client method through the factory interface. 

As a result, the client method will only need to know about the factory interface and the interfaces of the product families to instantiate and use the set of classes it is intended to be dealing with.

Example:

The diagram below illustrates the previously described solution applied to envelopes and stamps.

image

Advantages of this approach:

·         Different sets of classes (incompatible in terms of a business model used) are separated from each other;

·         The business logic uses just the set of classes it needs and knows nothing about any other sets;

·         The business logic doesn’t need to know about concrete classes. That is to say that the types of classes returned from repositories may be changed without any need to change the logic.

 

Approach 2: we delegate business logic applying to a set of classes to a base factory.

Besides the common factory interface shown in the previous solution we’ll use an abstract base factory class. The base factory class will implement common business logic shared by all sets of classes and contain abstract instantiation methods. The instantiation methods will be overridden in concrete factory classes.  The logic methods are declared as virtual in the base class and may also be overridden to provide business logic specific to a particular set of classes. We’ll also need some functionality shared by all members of a family. So we add basic classes one for each product family.

Now instantiation methods are organized in the way prescribed by the pattern known as Factory Method with Derivation. It enables us to implement the whole logic of processing instances of our classes inside factories and not to expose instantiation methods to the outside world if we don’t need to.

Example:

The diagram below shows the changed class relationships:

image

Here the client method’s been factored out of the model. Now factory classes carry additional responsibility for managing instances of the classes. They have some basic logic implemented inside their base class.

The mechanism of using factory classes remains almost unchanged. A factory is passed to the use case code via interface and the products it produces are all used in the use case via interfaces.

Here is the implementation of this approach in C#:

    public interface IEnvelope

    {

        IList<IStamp> Stamps { get; }

    }

 

    public interface IStamp

    {}

 

    public interface IProductFactory

    {

        IEnvelope BuildStampedEnvelope();

    }

 

    public abstract class Stamp : IStamp

    {}

 

    public abstract class Envelope : IEnvelope

    {

        private IList<IStamp> _Stamps;

 

        public IList<IStamp> Stamps

        {

            get

            {

                if (_Stamps == null)

                    _Stamps = new List<IStamp>();

                return _Stamps;

            }

        }

    }

 

    public abstract class ProductFactory : IProductFactory

    {

        public virtual IEnvelope BuildStampedEnvelope()

        {

            IEnvelope newEnvelope = CreateEnvelope();

            IStamp newStamp = CreateStamp();

            newEnvelope.Stamps.Add(newStamp);

            return newEnvelope;

        }

 

        protected abstract IEnvelope CreateEnvelope();

 

        protected abstract IStamp CreateStamp();

 

    }

 

    public class DhlEnvelope : Envelope

    {}

 

    public class UpsEnvelope : Envelope

    {}

 

    public class DhlStamp : Stamp

    {}

 

    public class UpsStamp : Stamp

    {}

 

    public class DhlProductFactory : ProductFactory

    {

        protected override IEnvelope CreateEnvelope()

        {

            return new DhlEnvelope();

        }

 

        protected override IStamp CreateStamp()

        {

            return new DhlStamp();

        }

    }

 

    public class UpsProductFactory : ProductFactory

    {

        protected override IEnvelope CreateEnvelope()

        {

            return new UpsEnvelope();

        }

 

        protected override IStamp CreateStamp()

        {

            return new UpsStamp();

        }

    }

 

Consider that we have an ability to make logic methods (like the one used to build stamped envelopes) company-specific. We only need to override the method in the respective factory class.

C# also allows us to keep both the default and company-specific logic methods available to the client by hiding default methods instead of overriding them. The following code sample shows how to do that.

 

    public class DhlProductFactory : ProductFactory

    {

        protected override IEnvelope CreateEnvelope()

        {

            return new DhlEnvelope();

        }

 

        protected override IStamp CreateStamp()

        {

            return new DhlStamp();

        }

 

        public new IEnvelope BuildStampedEnvelope()

        {

            IEnvelope newEnvelope = CreateEnvelope();

            newEnvelope.Stamps.Add(new DhlStamp(){Text="DHL Stamp"});

            return newEnvelope;

        }

    }

 

    public interface IStamp

    {

        string Text { get; set; }

    }

 

Now if we run the following code the text “DHL Stamp” will be sent to the output:

 

DhlProductFactory mailProductsFactory = new DhlProductFactory();

IEnvelope stampedEnvelope = mailProductsFactory.BuildStampedEnvelope();

 

System.Console.WriteLine(stampedEnvelope.Stamps[0].Text);

System.Console.ReadKey();

 

As a contrast, if we change the first line of the above code to the following one the output will be blank:

ProductFactory mailProductsFactory = new DhlProductFactory();

 

 

Advantages of this approach:

 

·         All the advantages of the first approach;

·         Ability to use set-specific logic in a use case with the use case code still knowing nothing about different sets of classes;

·         Ability to use default logic for all the sets of classes for which no overriding set-specific logic has been implemented.

 

Approach 3: we implement one parameterized creation method for multiple class families sharing a common interface.   

 

This approach is an extension of either the first or the second of the previously described approaches. If we use the second approach as the base one then the resulting process of classes’ instantiation will implement the Parameterized Factory Method pattern as shown below.

image

In this example the method CreateStamp of the class DhlProductFactory will return a new StampDhl for a “DhlConventional” enumeration value passed as a parameter, a SpecialStampDhl for “SpecialDhl” and a null for anything else. 

 

Advantages of this approach:

 

·         We don’t have to make a new method in every factory class (and change the factory interface)  every time we need to introduce a new product class

 

Limitations of this approach:

 

·         We’ll need the classes created by the parameterized factory method to implement a common interface.

 

 

Summary

 

General observations concerning the usability of the pattern

 

·         The pattern allows us to separate different sets of classes by design;

·         It restricts the way classes are instantiated;

·         Presents a simple solution with no need to use reflection;

·         If there’s a need to use a more flexible solution because the system of classes concerned is very changeable and complex an IoC Container is preferable

The above approaches are the simplest implementations of the Abstract Factory pattern. Though the pattern may be combined with Prototype Pattern elements or may use type parameters the use of these complex implementations of Abstract Factory is limited due to the existence of the patterns (like IoC Container) specifically designed to be used in complicated cases. However, the use of more sophisticated patterns may be redundant in simple cases and hurt the performance of the system. The final choice is left to the designer.