Factory Method is a creational design pattern, whose intent (according to Design Patterns: Elements of Reusable Object-Oriented Software) is to:
“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”
Do you want to know more about design patterns? Check reference [1] at the bottom for extra reading.
In this post, we will consider a variation of the pattern referred as parameterized factory method. This variation just refers to the first part of the intent, that is, “define an interface for creating an object”. Why is that? What about the rest of the intent? Well, there are times in which sub-classing is not possible or not suitable; nonetheless, it is useful to define a factory method to encapsulate the creation of an object.
Let’s proceed with the example we used in our previous post (template design pattern); that is: Design an application that allows drawing different styles of houses (ei. country house, city house) using ASCII art.
The GUI of our app contains some VCL components as you can see in the images below. Clicking the buttons will result in an ASCII house printed out in the memo area. Depending on the Draw… button being clicked, the house will look differently: it can be a country house or a city house.
Figure 1: A Country House (Draw Country House button was clicked).
Figure 2: A City House (Draw City House button was clicked).
Behind the scenes, assume that we have implemented the following class hierarchy:
THouse
is an abstract
class that defines several methods for displaying the different parts of the house [2] or the house as a whole [3].
TCountryHouse
and TCityHouse
are two concrete (non-abstract) classes that actually override some of the methods of its superclass, allowing this way different look-and-feel; that is, a house in the country and a house in the city look differently.
When a button is clicked, the corresponding OnClick
event is triggered. We have to provide logic (source code) to define what the button is going to do in response to the OnClick
event. Using fancy words, we have to implement the event handler for the OnClick
event. I have created the following event handler which is going to be shared by both buttons [4]:
procedure TfrmMain.btnDrawGenericHouseClick(Sender: TObject);
var
House: THouse;
begin
if not(Sender is TButton) then Exit;
if TButton(Sender).Name = 'btnDrawCountryHouse' then
House:= TCountryHouse.Create
else if TButton(Sender).Name = 'btnDrawCityHouse' then
House:= TCityHouse.Create;
with House do
begin
case cmbHasChimney.ItemIndex of
0: HasChimney:= True;
1: HasChimney:= False;
end;
memFrame.Text:= BuildIt;
Free;
end;
end;
This works of course, but it lacks flexibility. What happens if we want to add a third button, or a fourth, or a five in order to add new types of houses? In that case, you will have to rework the code above and the fragment in blue will grow up big as new house types are added. You will end up coding something like this:
if TButton(Sender).Name = 'btnDrawCountryHouse' then
House:= TCountryHouse.Create
else if TButton(Sender).Name = 'btnDrawCityHouse' then
House:= TCityHouse.Create
else if TButton(Sender).Name = 'btnDrawDogHouse' then
House:= TDogHouse.Create
else if TButton(Sender).Name = 'btnDrawTreeHouse' then
House:= TTreeHouse.Create
else if TButton(Sender).Name = 'btnDrawGhostHouse' then
House:= TGhostHouse.Create;
That is the only part changing within the method. Why don’t we take that chunk of code and place it somewhere else? For that, we can create a factory method. The factory method will take one parameter to decide the concrete house type to instantiate [4]. The return type of the factory method will be THouse
, because THouse
enfolds all the other house subtypes. What about this?
function TfrmMain.MakeHouse(Sender: TObject): THouse;
begin
if TButton(Sender).Name = 'btnDrawCountryHouse' then
Result:= TCountryHouse.Create
else if TButton(Sender).Name = 'btnDrawCityHouse' then
Result:= TCityHouse.Create
else if TButton(Sender).Name = 'btnDrawDogHouse' then
Result:= TDogHouse.Create
else if TButton(Sender).Name = 'btnDrawTreeHouse' then
Result:= TTreeHouse.Create
else if TButton(Sender).Name = 'btnDrawGhostHouse' then
Result:= TGhostHouse.Create;
end;
MakeHouse
is a parameterized factory method. It is called factory because its only purpose in life is to create an object. It is adorned with the parameterized term, because it takes a parameter (Sender
) to decide the concrete class to instantiate.
What does this mean to our previous code (event handler method)? Well, see it for yourself:
procedure TfrmMain.btnDrawGenericHouseClick(Sender: TObject);
var
House: THouse;
begin
if not(Sender is TButton) then Exit;
House:= MakeHouse(Sender);
with House do
begin
case cmbHasChimney.ItemIndex of
0: HasChimney:= True;
1: HasChimney:= False;
end;
memFrame.Text:= BuildIt;
Free;
end;
end;
Now, as you can see, the event handler will remain the same no matter how many different subtypes of houses we have (add). Everything about the creation of a house object is isolated in the factory method.
One final comment: Delphi is a hybrid programming language, allowing both structured and object oriented programming. In Delphi, we can have functions outside all classes’ scope. That’s not very Object Oriented. Isn’t it? Besides, you can’t do that in languages like Java, Python, Ruby or C#.
In general, you should have a Factory class containing the factory method. In this particular Delphi example, you might argue that we are just adding code for nothing. Yes, I give you that. But there are cases, in which having a hierarchy of factory classes comes naturally and the subfactories decide the concrete object to be created. We’ll cover that scenario in future posts. :-)
Notes
[1] Books on Design Patterns:
[2] BuildFloor()
, BuildWalls()
, BuildRoof()
, BuildChimney()
. These methods draw out the floor, walls, roof and chimney (if any) respectively.
[3] BuildIt()
: This method calls the methods in [2] and as a result, it draws the whole house.
[4] You can define the event handler for the buttons at design time using Delphi’s Object Inspector.