There are many sites out there showing examples of the Simple Factory Pattern.
This blog entry takes the simple examples a little further by giving 3 different ways to make the decision on which Concrete Object is returned.
While I have experience with all 3, the use of reflection seems to be the most powerful one.
Alot of times, you may be using this pattern (and specifically the #3 option) without really knowing it.
The Enterprise Library from Microsoft is one very well known Framework. The DataAccess objects actually are using reflection to determine which concrete Database object (Microsoft.Practices.EnterpriseLibrary.Data.Database) to use. The Microsoft.Practices.EnterpriseLibrary.Data.Database is an abstract class, and the concrete class is determined by some string settings in the dataConfiguration.config file.
(Look closely at the line
<databaseType name="Sql Server" type="Microsoft.Practices.EnterpriseLibrary.Data.Sql.SqlDatabase, Microsoft.EntLib.Data" />
That’s reflection giving back a concrete version of a Microsoft.Practices.EnterpriseLibrary.Data.Database object)
Here is another link, discussing the ASP.NET 2.0 Provider Model.
So what are the 3 methods?
1. Using a "key". Alot of web samples use this method. The example here uses a string letter to give back a concrete IAnimal.
2. Use the Environment. I have to thank a previous colleague for showing me this one. Well, he actually showed me the Factory Pattern to begin with. So there’s some dap for him.
This example will return a different concrete class based on Web Environment or a Winforms environment.
3. Using reflection. You can setup the concrete class in a configuration file. The code will use the Interface or Abstract Class, but you can switch out the functionality with just a tweak of a config file.
This is very flexible, and really keeps your options open for future development.
In fact, the concrete class can live in its own assembly, just as long as it implements the Interface or Abstract class. My example shows how you can switch out to a different "RateQuoter" (the mock example uses PostalService and UPS to give different shipping rates). The switch can occur without recompile, and only a few tweaks of a configuration file.
Thanks to all my colleagues with their mad OO skilllzzzz.
Here’s some code snipplets, the download will give you a complete C# code sample to show you how to better use the Factory Design Pattern.
As bonus material, you’ll also see a:
Web version of the Singleton Pattern.
A configuration handler, for encapsulating information in your App.config or Web.config file.
#1 Key Method
public class AnimalFactory
{
public static IAnimal GetAnimal(string key)
{
switch (key.ToUpper())
{
case "B":
return new Bird();
{
public static IAnimal GetAnimal(string key)
{
switch (key.ToUpper())
{
case "B":
return new Bird();
case "C":
return new Cat();
return new Cat();
case "D":
return new Dog();
return new Dog();
default:
throw new ArgumentException("The AnimalFactory was given a Bad Key");
}
throw new ArgumentException("The AnimalFactory was given a Bad Key");
}
}
}
}
#2 Environment Method
public class ObjectHolderFactory
{
private ObjectHolderFactory()
{
}
{
private ObjectHolderFactory()
{
}
public static IObjectHolder GetObjectHolder( )
{
{
//This is a perfect example of a Simple Factory need.
//When in a non web arena, the singleton is a simple
//Hashtable or HybridDictionary object
//Hashtable or HybridDictionary object
//When I am in the web arena, I need to piggyback off the
//web Session object, aka, the WebSessionObjectHolder
//uses session variables to store the singleton
//web Session object, aka, the WebSessionObjectHolder
//uses session variables to store the singleton
//Singleton
if ( null == System.Web.HttpContext.Current ) // Add a reference to System.Web.dll
{ //Non Web Environment
return InMemoryObjectHolder.GetInstance();
}
else
{ //Web Environment
return WebSessionObjectHolder.GetInstance();
}
if ( null == System.Web.HttpContext.Current ) // Add a reference to System.Web.dll
{ //Non Web Environment
return InMemoryObjectHolder.GetInstance();
}
else
{ //Web Environment
return WebSessionObjectHolder.GetInstance();
}
}
}
#3 Reflection
public class RateQuoterFactory
{
{
private static readonly string CONFIG_SECTION_NAME = "RateQuotersSectionName";
private RateQuoterFactory()
{
}
{
}
public static IRateQuoter GetARateQuoter()
{
//The RateQuoterSettings encapulates the information found in the App.Config file
//The "Handler" reads the xml … to create a RateQuoterSettings instance.
RateQuoterSettings settings = ((RateQuoterSettings)System.Configuration.ConfigurationSettings.GetConfig(CONFIG_SECTION_NAME));
{
//The RateQuoterSettings encapulates the information found in the App.Config file
//The "Handler" reads the xml … to create a RateQuoterSettings instance.
RateQuoterSettings settings = ((RateQuoterSettings)System.Configuration.ConfigurationSettings.GetConfig(CONFIG_SECTION_NAME));
//Now you have a RateQuoterSettings instance.
//use the assembly and class name from the RateQuoterSettings instance. to dynamically create the object
return CreateInstance( settings.AssemblyName, settings.ClassName , settings.ShippingCompanyHomeState );
}
//use the assembly and class name from the RateQuoterSettings instance. to dynamically create the object
return CreateInstance( settings.AssemblyName, settings.ClassName , settings.ShippingCompanyHomeState );
}
private static IRateQuoter CreateInstance( string assemblyName, string className , string homeState)
{
{
IRateQuoter returnObject = null;
Assembly assem = Assembly.Load( assemblyName );
if(null != assem)
{
Assembly assem = Assembly.Load( assemblyName );
if(null != assem)
{
Type objectType = assem.GetType( className , true , true );
//The use of "homeState" is here… to show how the CreateInstance can have non default constructors.
//Notice the second argument is an array of objects.. the array of objects … needs to match one of the contructor-method-signatures
returnObject = (IRateQuoter)Activator.CreateInstance( objectType , new object[] {homeState} );
//Notice the second argument is an array of objects.. the array of objects … needs to match one of the contructor-method-signatures
returnObject = (IRateQuoter)Activator.CreateInstance( objectType , new object[] {homeState} );
}
return returnObject;
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="RateQuotersSectionName" type="GranadaCoder.Applications.FactoryPatternExamples.FactoryWithReflection.ConfigurationLib.RateQuoterHandler,GranadaCoder.Applications.FactoryPatternExamples" />
</configSections>
<RateQuotersSectionName>
<configuration>
<configSections>
<section name="RateQuotersSectionName" type="GranadaCoder.Applications.FactoryPatternExamples.FactoryWithReflection.ConfigurationLib.RateQuoterHandler,GranadaCoder.Applications.FactoryPatternExamples" />
</configSections>
<RateQuotersSectionName>
<rateQuoterObject enabled="true" assemblyName="GranadaCoder.Applications.FactoryPatternExamples.ConcreteObjectsOutsideBaseAssembly"
className="GranadaCoder.Applications.FactoryPatternExamples.ConcreteObjectsOutsideBaseAssembly.ConcreteRateQuoters.UPSRateQuoter" ShippingCompanyHomeState="NC"/>
</configuration>
So there’s the summary. Check out the code, and see for yourself.
Right click HERE and click ‘Save As’.