8/26/2007

Factory Method Pattern

If you are asked to create a simle factory for creating objects,
you would start (or rather end up ) with something like this:
ProductFactory {
Product getProduct(int case) {
if(case== 1)
return new getProduct1();
else
return new getProduct2();
}
}

However, more often than not, from a requester's perspective, the product that he is looking for will be more polished than 'new Product()' that is to say the 'new Product()' would undergo some process before it is delivered.
ProductFactory {
Product getProduct(int case) {
Product ins = null;
if(case== 1)
ins = new Product();
else
ins = new Product1();
doSomethingWithProduct(ins);
doSomethingElseWithProduct(ins);
return ins;
}
doSomethingWithProduct(AbstractProduct ... ) {
}
doSomethingElseWithProduct(AbstractProduct ... ) {
}
}

We are using AbstractProduct instead of the actual Product, why?
It's for the same reason that we prefer
List l = new ArrayList() to
ArrayList l = new ArrayList();

AbstractProduct is an abstraction of Product which is of interest to the factory and
its users. It makes sure the same factory can work when new Products are introduced as long as they are instances of the same abstraction. It also means the factory returns abstractions not actual implementations so users of this factory are also lightly coupled.
Once we have an implementation like above, it can be used as
ProductFactory pf = new ProductFactory();
pf.getProduct();

Observe that the factory is tightly coupled with actual implementations of Products.
Now consider introducing a new factory, so that a client (requester) has the option to choose one of these factories where he wants the product from. That choice should be construed as the Context in which the product is created/requested.
e.g. a Spreadsheet instance from MSOffice or one from OpenOffice. Client would makje a decision to go for one of these. The choice is basically boils down to selecting one of MSOfficeSpreadsheetFactory or OOfficeSpreadsheetFactory.
However, before the spreadsheet is returned, it should be assigned a default name and should also be saved in a temporary location (this post-process is henceforth referred as polishing). This has to happen irrespective of whether it is MSO or OO spreadsheet.
At this stage we will have these two factories:
class MSOfficeSpreadsheetFactory {
...
}
class OOfficeSpreadsheetFactory {
...
}

We can also see two methods working on spreadsheets:
assignName(Spreadheet ...){}
tempSave(Spreadsheet ...){}

As we can see these methods work on Spreadsheet (an abstraction like AbstractProduct)
so we should definitely abstract it out of MS and OO factory classes and thus make it reusable.
SpreadsheetFactory {
assignName(Spreadheet ... ) {
}
tempSave(Spreadheet ... ) {
}
}
MSOfficeSpreadsheetFactory extends SpreadsheetFactory {
Spreadsheet createSpreadsheet(int type) () {
Spreadsheet s = new Excel();
assignName(s);
tempSave(s);
}
}
}
OOfficeSpreadsheetFactory extends SpreadsheetFactory {
Spreadsheet createSpreadsheet(int type) () {
Spreadsheet s = new Calc();
assignName(s);
tempSave(s);
}
}

At this stage, the requester would look something like this:
MSOfficeSpreadsheetFactory msf =
new MSOfficeSpreadsheetFactory();
Spreadsheet spr1 = msf.createSpreadsheet(...);
OOfficeSpreadsheetFactory osf =
new OOfficeSpreadsheetFactory();
Spreadsheet spr2 = osf.createSpreadsheet(...);

If we abstract out createSpreadsheet(...) we can reduce our dependency on actual Factory implementations.
SpreadsheetFactory {
assignName(Spreadheet ... ) {
}
tempSave(Spreadheet ... ) {
}
public abstract Spreadsheet createSpreadsheet(...);
}
...
SpreadsheetFactory msf =
new MSOfficeSpreadsheetFactory();
Spreadsheet spr1 =
msf.createSpreadsheet(...);
SpreadsheetFactory osf =
new OOfficeSpreadsheetFactory();
Spreadsheet spr2 =
osf.createSpreadsheet(...);

So far so good. Lets take a moment and analyze where things could get complicated with this approach.
What happens when a new Factory is introduced and/or and new polishing procedure is introduced. This would require a change in the SpreadsheetFactory and all its concrete implementations as implementations are using superclass methods. We can work this out by moving the polishing out of implementations and thus leaving them responsible for creation alone.
Instead of invoking createSpreadsheet() which is implemented in the subclass
introduce another method getSpreadsheet() in SpreadsheetFactory. This moves back the creation control to the SpreadsheetFactory, the superclass.
SpreadsheetFactory {
assignName(Spreadheet ... ) {
}
tempSave(Spreadheet ... ) {
}
public Spreadsheet getSpreadsheet() {
Spreadsheet s = createSpreadsheet();
assignName(s);
tempSave(s);
return s;
}
abstract Spreadsheet createSpreadsheet();
}

What we gain from this approach:
The factory SpreadsheetFactory is not dependent on actual implementations of Spreadsheets. Still, the factory knows how to create and return implementations of Spreadsheets. How does that happen...
The factory is delegating the actual instantiation to its subclass. The subclass is selected by the requester (which is basically the Context selection). The SpreadsheetFactory class would not be impacted by addition of new implementations.
It's important to note that the essence of this pattern lies in how the factory should be designed so that the factory itself is not coupled with what it creates and hence can be used to create products depending on the context that it operates in. See it from the point of view of the getSpreadsheet() method, not from the point of view of the client.
This pattern is usually applied to scenarios where factories return a certain type of product.

No comments:

Post a Comment