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); |