One of the problems with using a factory is that typically you have to change your factory when you add new classes of objects that can be created. I decided that this was not good enough, so instead used a configuration file to list out the types that the factory can create. This is commonly referred to as a Service Locator. This post describes how to implement one.
This uses the System.Activator class to create objects of the specified type at runtime. The configuration file just uses AppSettings to store key/value pairs of type names and fully qualified (with namespace) type names. As more classes are added that need to be created by this factory, all that is required is to add a new configuration file key/value pair for each new class.
Since this is done at runtime, I would assume there is a performance hit doing it this way. But it's yet another tradeoff between extensibility and performance. What is right for your specific application will of course depend on the level of performance that you need.
This example will use an abstract Report class and three concrete classes that derive from Report.
First of all, we have the abstract class which all classes we will instantiate derive from:
abstract class Report
{
protected ReportViewer reportViewer;
protected ReportCreationParameters creationParameters;
protected Report(ReportCreationParameters parameters)
{
creationParameters = parameters;
}
protected abstract void DisplayReport (ReportDisplayParameters parameters);
}
Now an example of a derived class:
class ReportTypeA : Report
{
public ReportTypeA(ReportCreationParameters parameters) : base(parameters)
{
// Create report based on the parameters passed in
}
protected override void DisplayReport(ReportDisplayParameters parameters)
{
// Display method specific to report type A
}
}
The classes referred to above are very simple, and will use Reporting Services types and settings eventually. The ReportCreationParameters class contains settings that will be used to create the report, while the ReportDisplayParameters class contains settings that will be used to display the report.
class ReportViewer { }
class ReportCreationParameters
{
private string parameter1;
private string parameter2;
public string Parameter1
{
get { return parameter1; }
set { parameter1 = value; }
}
public string Parameter2
{
get { return parameter2; }
set { parameter2 = value; }
}
}
class ReportDisplayParameters { }
Here is the AppSettings section of the configuration file:
<appSettings>
<add key="ReportTypeA" value="DotNetMafia.ReportTypeA"/>
<add key="ReportTypeB" value="DotNetMafia.ReportTypeB"/>
<add key="ReportTypeC" value="DotNetMafia.ReportTypeC"/>
appSettings>
Now the interesting part, the factory, which uses System.Activator and AppSettings to instantiate the types specified in the configuration file. I used the CreateInstance overload that takes the class type and an array of objects, which are the parameters to the constructor (ReportCreationParameters in this example):
static class ReportFactory
{
public static Report CreateReport(string reportType, ReportCreationParameters parameters)
{
string reportClassType = ConfigurationManager.AppSettings[reportType];
return (Report)Activator.CreateInstance(Type.GetType(reportClassType), new object[] { parameters });
}
}
So then from the main program I use this code to instantiate whatever class I need:
ReportCreationParameters parameters = new ReportCreationParameters();
parameters.Parameter1 = "TypeAParameter1";
parameters.Parameter2 = "TypeAParameter2";
ReportFactory.CreateReport("ReportTypeA", parameters);
So there you have it. This is nothing profound or even original, but in my opinion it is a good way to use factories with extensibility in mind.