Friday, September 08, 2006

Simple factory pattern made simpler

A Simple Factory pattern returns an instance of one of several possible classes depending on the data provided to it.

Last week I talked about a technique that allows you to reduce (and effectively reuse) code when you have a pattern of 2..n values, where for each value you want to execute a different method with the same signature

if (someValue == SomeEnum.Type1)
Method1("value 1");
else if (someValue == SomeEnum.Type2)
Method2("value 2");
else if (someValue == SomeEnum.Type2)
Method3("value 3");

while the method is valuable by itself, there is a pattern where it fits perfectly, this is the simple factory method; let's start with a reports example.

Suppose we have a Base Report class, and two implementations of it:

abstract class BaseReport {
public BaseReport() {
Console.WriteLine("Base Report Created");
}
public abstract void Execute();
}
class Report1 : BaseReport {
public Report1():base() {
Console.WriteLine("Report1 created");
}
public override void Execute() {
Console.WriteLine("Report1");
}
}
class Report2 : BaseReport {
public Report2():base() {
Console.WriteLine("Report2 created");
}
public override void Execute() {
Console.WriteLine("Report2");
}
}

enum ReportType {
Report1,
Report2
}
To use the arrays of methods, we have to declare our delegate to instantiate the reports:
delegate BaseReport ReportCreatorDelegate();
Then we have our factory class that contains:

  • An array of methods

  • a method to instantiate each report

  • The method to execute the requested report, depending on the parameters
class ReportGenerator {
static BaseReport CreateReport1() {
return new Report1();
}
static BaseReport CreateReport2() {
return new Report2();
}
static ReportCreatorDelegate[] reports;
static ReportGenerator() {
reports = new ReportCreatorDelegate[2];
reports[0] = new ReportCreatorDelegate(CreateReport1);
reports[1] = new ReportCreatorDelegate(CreateReport2);
}
public static BaseReport Execute(ReportType reportType) {
return reports[(int)reportType]();
}
}

Then here's how we use this code:

BaseReport report1 = ReportGenerator.Execute(ReportType.Report1);
report1.Execute();

BaseReport report2 = ReportGenerator.Execute(ReportType.Report2);
report2.Execute();
Console.ReadLine();
Update: Pablo commented "the fact that calling the factory with the type you want to create is kinda the same as creating it yourself", it is true, I could've just created an instance directly my self there, but this was just a bad example from my part, that value could come from a drop down select list, from a configuration file, etc, I just did a bad job showing you how you could use the code.

This particular example is very light weight and works great to implement this simple pattern, but it has the limitation that it works only for values that can be converted to integerAyende and Steven pointed out that the same technique can be implemented using Dictionaries, this would allow us to have other types as the index of our collection of methodsOn my next posts I'll show you this technique, using Dictionaries of methods

You can find the full source code for this and my previous example here

2 comments:

Pablo said...

Hi,

maybe it's because I'm a Java programmer, but that implementation of a Factory is not good (politely speaking). Don't you have a Class.newInstance() equivalent?

The main problem is that if you want to add a new type of Enum, you have to add a new index to the array, so you modify the factory for each new type.

That, and the fact that calling the factory with the type you want to create is kinda the same as creating it yourself. I think the whole idea of the factory pattern is the client class not knowing wich concrete class is being created.

Well, just my thought.. again, maybe it's a good implementation in C#, but it does have some conceptual errors anyway.

Regards,
Pablo.

BlackTigerX said...

This particular implementation is for a simple pattern factory, and if you wanted to add a product to the factory, you would have to modify the factory anyway regardless of how you implement it

it is a very specific case where you can use this particular example, but works really well

in this example yes, I'm passing the type my self, but that could come from a configuration file, a web service call, etc