Click here to Skip to main content
15,884,472 members
Articles / Programming Languages / Delphi

Parameterized Factory Method in Delphi

Rate me:
Please Sign up or sign in to vote.
4.80/5 (2 votes)
28 Dec 2012CPOL4 min read 8K   2   1
Parameterized factory method in Delphi

Factory Method is a creational design pattern, whose intent (according to Design Patterns: Elements of Reusable Object-Oriented SoftwareImage 1) 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.

Factory Pattern Example (Delphi) – Country House

Figure 1: A Country House (Draw Country House button was clicked).

Factory Pattern Example (Delphi) – City House

Figure 2: A City House (Draw City House button was clicked).

Behind the scenes, assume that we have implemented the following class hierarchy:

Factory Pattern Example (Delphi) – Class diagram

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]:

Delphi
//OnClick event handler for the buttons
 procedure TfrmMain.btnDrawGenericHouseClick(Sender: TObject);
 var
   House: THouse;
 begin
   //If the "Sender" object is not a button do nothing
   if not(Sender is TButton) then Exit;
 
  //The concrete house object being created depends
   //on the button being clicked
   if TButton(Sender).Name = 'btnDrawCountryHouse' then
     House:= TCountryHouse.Create
   else if TButton(Sender).Name = 'btnDrawCityHouse' then
     House:= TCityHouse.Create;
 
  with House do
   begin
     //cmbHasChimney is a combo box (TComboBox) that allows
     //to decide whether to add a chimney or not to the house
     case cmbHasChimney.ItemIndex of
       0: HasChimney:= True;
       1: HasChimney:= False;
     end;
     //"memFrame" is a memo (TMemo) used to print out the house.
     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:

Delphi
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?

Delphi
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:

Delphi
//OnClick event handler for the buttons
 procedure TfrmMain.btnDrawGenericHouseClick(Sender: TObject);
 var
   House: THouse;
 begin
   //If the "Sender" object is not a button do nothing
   if not(Sender is TButton) then Exit;
 
  //The concrete house object being created depends
   //on the button being clicked
   House:= MakeHouse(Sender); //See our factory method in action
 
  with House do
   begin
     //cmbHasChimney is a combo box (TComboBox) that allows
     //to decide whether to add a chimney or not to the house
     case cmbHasChimney.ItemIndex of
       0: HasChimney:= True;
       1: HasChimney:= False;
     end;
     //"memFrame" is a memo (TMemo) used to print out the house.
     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:

  • Design Patterns: Elements of Reusable Object-Oriented SoftwareImage 6
  • Head First Design PatternsImage 8
  • Object Models: Strategies, Patterns, and Applications (2nd Edition)Image 10

[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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
GeneralMy vote of 4 Pin
Member 1433972827-Apr-19 22:30
Member 1433972827-Apr-19 22:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.