Search  
Friday, September 10, 2010 ..:: Articles » GOF Creational Patterns with C# ::..   Login
 Creational Patterns
Introduction | Factory | Abstract factory | Builder | Prototype | Singleton | Links
 
Show as single page

Factory Pattern

To illustrate the benefits of the Factory pattern my demonstration client program uses an object that provides a public SayWho behaviour, which returns a string showing the object's name. Here is an example of it's use:

listBox1.AppendText(Product.SayWho());

The aim is to allow the client to instantiate and use this object in a manner that is accepting of change. The object will become the product of a “factory”. If there are various flavours of product we want to write reuable generic client code rather than have untidy switch blocks everywhere. We want to be able to provide a new implementation of the initial "product" class's behaviour, and have the same client still able to run without change, now using the new product class.

We also wish to protect our client from any complexities or background work that may be involved in creating an instance of our object.

To achieve this first we define a base class for our "product" classes.

        // --- Abstract product
        abstract public class ProductBase {
                public string SayWho() {
                        return "\n   * " + this.ToString();
                }
        }

We then create a factory class to instantiate "product" objects. This is done so that the instantiation of the "product" class is outsourced, away from our client, meaning that our client no longer need know the class name of the object which it is using. In this example I have passed a parameter into the factory's MakeProduct method so that the factory could be later changed to be more flexible about the flavour of product that it instantiates.

        // --- Abstract factory
        public abstract class   FactoryBase {
                abstract public ProductBase     MakeProduct(int t);
        }

        // --- Concrete factories
        public class ConcreteFactory_A: FactoryBase {
                public override ProductBase     MakeProduct(int t) {
                        return new ConcreteProduct_A();
                }
        }

We can now code our client as follows. Note that the "product" variable is defined using the abstract base class. rather than the concrete class which will be instantiated. We will be able to change the class of product that the client is using by issuing a new version of the ConcreteFactory class which returns a different class of product. As long as the new product implements the same signature as the old product our client will not notice the difference.

        FactoryBase Fact = new ConcreteFactory_A();
        ProductBase Product = Fact.MakeProduct(0);
        listBox1.AppendText(Product.SayWho());

To get a little leverage out of the factory pattern we need to need to define a new generation, but similar product class. We will create a “B” factory to make the new generation B-type objects. This factory can make two types of object, one extends the original interface, so that it now also provides a SayWhen behaviour. Firstly we should look at the concrete implementation of the products.

   // --- Interface for extended product

   public interface IExtendedProduct 
   {
                    string SayWhen();
   }

   // --- Concrete products

   public class ConcreteProduct_A: ProductBase {}

   public class ConcreteProduct_B_1: ProductBase {}

   public class ConcreteProduct_B_2: ProductBase, IExtendedProduct 
   {
           public string SayWhen() 
           {
                return (String.Format("\n   * {0:f}",System.DateTime.Now));
           }
   }

Then the new factory.

   public class ConcreteFactory_B: FactoryBase {
       override public ProductBase     MakeProduct(int t) {
           switch (t) {
               case 1:
                  return new ConcreteProduct_B_1();
               case 2:
                  return new ConcreteProduct_B_2();
               default:
                  throw new Exception("Invalid product request");
           }
       }
   }

The client can use the new products with an unchanged "Product.SayWho()" call. It can also be coded to recognise the products with the extended behaviour, and use this behaviour when appropriate.

  Fact = new ConcreteFactory_B();
  Product = Fact.MakeProduct(1);
  listBox1.AppendText(Product.SayWho());
  if (Product is IExtendedProduct)
     listBox1.AppendText(((IExtendedProduct)Product).SayWhen());}

If version 2 of our application requires modification to a product class we can produce a new factory to produce the new version objects. Another approach is to descend from the version 1 factory, and handle any new version products from the descendant, while letting the version 1 factory continue to produce an heritage products.

    public class ConcreteFactory_B_V2: ConcreteFactory_B {
        override public ProductBase     MakeProduct(int t) {
            switch (t) {
                case 3:
                    return new ConcreteProduct_B_1_V2();
                default:
                    return base.MakeProduct(t);
            }
        }
    }

Here is the output from our client demonstrating the above points:


And here is a diagram showing the pattern's particants and interactions:


We can introduce some runtime flexibility by using Reflection along with the factory pattern. I will introduce an xml runtime configuration file with the following contents.

<Factories>
        <Factory name="ConcreteFactory_C">
                <Products>
                        <Product name="1" class="Factory.ConcreteProduct_C">                            
                        </Product>
                </Products>
        </Factory>
</Factories> 

I will call this file “RuntimeConfig.xml”. My factory can then parse this file to obtain the qualified name (class and namespace name) of the concrete product that it will create for the client.

/* Here we increase runtime flexibility by using a runtime 
 * configuration file to control which concrete class is 
 * produced by our factory. */
public class ConcreteFactory_C: FactoryBase {
    override public ProductBase     MakeProduct(int t) {
        const string xmlDoc = @"RuntimeConfig.xml";
        string factoryName = "ConcreteFactory_C";

        // Load the runtime configuration file
        XmlDocument document = new XmlDocument();
        try {
            XmlTextReader reader = new XmlTextReader(
                 new FileStream(xmlDoc, FileMode.Open));
            reader.WhitespaceHandling = WhitespaceHandling.None;
            document.Load(reader);
            reader.Close();
        } 
        catch (FileNotFoundException ex) {
            throw new ApplicationException
                      ("Runtime config file named " 
                       + xmlDoc + " needed.", ex);
        } 
        catch (XmlException ex) {
            throw new ApplicationException(
               String.Format
                     ("Config file named {0} is poorly formed; {1}"
                     ,xmlDoc, ex.Message), ex);
        }

                                     
        /* Obtain the class name for the product that the 
         * caller has requested. */
        string search = String.Format(
            @"Factories/Factory[@name='{0}']/Products/Product[@name='{1}']",
            factoryName, t);
        string className = "";
        try {
            XmlNodeReader nodeReader  
                = new XmlNodeReader(document.SelectSingleNode(search));                               
            nodeReader.MoveToContent();
            className = nodeReader.GetAttribute("class");
            if (className == "")
                throw new ApplicationException(String.Format(
                    "Class name is blank in config file {0} for product {1} for factory {2}",
                     xmlDoc,t,factoryName,t));
        } 
        catch (NullReferenceException) {
            throw new ApplicationException(String.Format(
                "No classname for product {0} in file {1} for factory {2}",
                 t,xmlDoc,factoryName));
        }

Our factory then uses Reflection as follows to create an instance of class that was named in the config file:

     Assembly assem = Assembly.GetExecutingAssembly();
     Type productType = assem.GetType(className);
     if (productType == null)
         throw new ApplicationException(String.Format("Cannot find class {0} 
mentioned in config file {1} for factory {2}, product {3}"
, className,xmlDoc,factoryName,t)); return (ProductBase)Activator.CreateInstance(productType);

Introduction | Page 2 of 7 | Abstract factory

      

Copyright 2005 by Primos Computer Services   Terms Of Use  Privacy Statement