• Blog
  • Understanding and Implementation of Factory Method Pattern in Delphi

Understanding and Implementation of Factory Method Pattern in Delphi

How we can use factory design pattern to ease creation of objects when there are multiple classes having similar implementation and/or implementing the same interface

Publish date:
Discover more of what matters to you

When you are writing simple or complex applications, you might need to create multiple classes inheriting the same class or implementing the same interface. This is easier when we have a small number of classes at the same level of inheritance but when more and more classes are introduced, it becomes more difficult to maintain the hierarchy and to support the creation of objects at many different places.

To solve this problem, the Factory Design Pattern was introduced. 

The Factory Pattern has two types.

  1. The Factory Method Pattern
  2. The Abstract Factory Pattern

In this article, we are going to have a look at the Factory Method – Design Pattern. We will explain what it actually is, what problems it solves, what pros and cons it has, and how we can implement it in Delphi.

Factory Method Pattern

The Factory Method Pattern solves problems of object creation and maintenance by separating the creation of objects from the concrete implementation of classes. This is done by creating a Factory class that contains methods for instantiate classes that implement the same interface or are inherited from the same class.

Below you can see an easily understandable example with code. The entire code explained below can be found here. <Link to website to download code>

Scenario with a code example:

Bill works as a programmer at an automated bakery that sells cheesecakes. His job is to write code that helps to create the type of cheesecake that a customer chooses. Easy job! Because, currently, there are only two types of cheesecakes that the bakery offers. Strawberry and Blueberry.

Bill started by writing classes for two types of Cheesecakes as you can see below and a function to create an instance of one of these two depending on the input received from the user. Bill wrote this function in the project file to make code easier to write and called it just there.

Unit Name: StrawberryCheeseCake.pas

123456789101112131415161718192021222324252627282930313233343536
unit StrawberryCheeseCake;
interface
type
TStrawberryCheeseCake = class(TObject)
public
procedure MakeCrust();
procedure AddLayer();
procedure AddFlavour();
procedure Bake();
End;
implementation
procedure TStrawberryCheeseCake.AddFlavour();
begin
WriteLn('Added Strawberry Flavour…');
end;
procedure TStrawberryCheeseCake.AddLayer();
begin
WriteLn('Added Layers on Strawberry Cheesecake…');
end;
procedure TStrawberryCheeseCake.Bake;
begin
WriteLn('Here''s your Strawberry Cheesecake…');
end;
procedure TStrawberryCheeseCake.MakeCrust;
begin
WriteLn('Making Crust for your Strawberry Cheesecake…');
end;
end.

Unit Name: BlueBerryCheeseCake.pas

123456789101112131415161718192021222324252627282930313233343536
unit BlueberryCheeseCake;
interface
type
TBlueberryCheeseCake = class(TObject)
public
procedure MakeCrust();
procedure AddLayer();
procedure AddFlavour();
procedure Bake();
End;
implementation
procedure TBlueberryCheeseCake.AddFlavour();
begin
WriteLn('Added Blueberry Flavour…');
end;
procedure TBlueberryCheeseCake.AddLayer();
begin
WriteLn('Added Layers on Blueberry Cheesecake…');
end;
procedure TBlueberryCheeseCake.Bake;
begin
WriteLn('Here''s your Blueberry Cheesecake…');
end;
procedure TBlueberryCheeseCake.MakeCrust;
begin
WriteLn('Making Crust for your Blueberry Cheesecake…');
end;
end.

Then, Bill needed to write the main function to get input from the user and instantiate one of these classes depending on that input.

The code can be found below.

File Name: CheeseCakeShop.dpr

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
program CheeseCakeShop;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
StrawberryCheesecake,
BlueberryCheesecake;
type
TCheeseCakeFlavour = (ckfStrawberry, ckfBlueberry);
procedure PrepareCheeseCake;
var
Flavor: TCheeseCakeFlavour;
FlavorStr: String;
Cheesecake: TObject;
begin
Writeln('What Flavor of Cheesecake do you want?');
Readln(FlavorStr);
Flavor := ckfStrawberry;
Cheesecake := Nil;
if FlavorStr = 'Strawberry' then
Flavor := ckfStrawberry
else if FlavorStr = 'Blueberry' then
Flavor := ckfBlueberry;
try
case Flavor of
ckfStrawberry:
begin
Cheesecake := TStrawberryCheeseCake.Create;
(Cheesecake as TStrawberryCheeseCake).MakeCrust;
(Cheesecake as TStrawberryCheeseCake).AddLayer;
(Cheesecake as TStrawberryCheeseCake).AddFlavor;
(Cheesecake as TStrawberryCheeseCake).Bake;
ReadLn;
end;
ckfBlueberry:
begin
Cheesecake := TBlueberryCheeseCake.Create;
(Cheesecake as TBlueberryCheeseCake).MakeCrust;
(Cheesecake as TBlueberryCheeseCake).AddLayer;
(Cheesecake as TBlueberryCheeseCake).AddFlavor;
(Cheesecake as TBlueberryCheeseCake).Bake;
ReadLn;
end;
end;
finally
Cheesecake.Free;
end;
end;
begin
try
PrepareCheeseCake;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

When Bill compiled and ran his code, he found out that it worked like a charm and he could see an output that varied depending on the input supplied.

Input: Strawberry

Input: Blueberry

Wow! Amazing!

Bill was very happy that his code was working correctly and the bakery where he was working at that moment started gaining popularity. It was growing and more people were recruited to perform specific tasks.

One of such new positions is CCFO – Chief Cheesecake Flavor Officer. This new person’s name is Jake and he looked creative. His responsibility is to create new flavors of Cheesecakes. Sounds interesting! Right?

He is doing his job well from the very first day and created a new flavor of Cheesecake which is “Mango” cheesecake. Beautiful!

One day, Jake came to Bill and asked: “Hey Bill, can you make changes to your code so that we can create this new Mango cheesecake and give it to our customers?”

Bill thought a little bit and said: “Sure. Why not?”

Bill knew exactly what to do.

He went ahead and introduced the following changes to his code

  1. Created a new class called “MangoCheeseCake” similar to the other two classes. 
  2. Added a new flavor called “ckfMango” type to the CheeseCakeFlavour enumeration 
  3. Added a MangoCheeseCake unit to the uses list of the project file and
  4. Introduced changes to the “PrepareCheeseCake()” function to dispatch Mango cheesecake if a customer selects it.

He built his project and found that his code works like a charm and became happy. Starting from the following day, customers at the bakery could get Mango Cheesecake if they chose it on the menu.

Jake was happy but Bill was happier that morning.

As Jake is creative, he couldn’t stop thinking about new flavors and experiments.

Just before the day ended, Jake came to Bill to speak about a new flavor of Cheesecake “Coffee” that he created and asked to change the code so that his new cheesecake could be sold if a customer selects it. Bill agreed but he had a rather sad face because he knew that he would need to perform the same repetitive steps again to make the new flavor available.

Going home, Bill couldn’t stop thinking about all the new flavors that Jake would create in the future and the same repetitive changes that he would need to introduce to that code.

While thinking about it, he also remembered the words of one of his computer programming professors from college.

He used to say: “If you write the same code once, encapsulate it. If you write the same code twice, add another layer of encapsulation. If you write the same code thrice, stop being a programmer.”

Bill couldn’t stop thinking about all the ways he could avoid this redundant code writing and make his code easier to write, change and maintain.

As he finished his dinner he recollected the Factory Method – Design Pattern that the same professor had explained.

He remembered that the Factory Method – Design Pattern is used to separate creation from implementation and can be a solution to the code redundancy problem.

The next morning, Bill came back to the bakery and took a look at his code. Since he recollected all the programming principles that the professor had explained in the college, he found out that he had a new approach to his code.

Now, just when he looked at his code his eyes captured all the units in the uses list of the project file and he realized that to add any new flavor of Cheesecake he would need to update the list of uses and even make changes to the project file. He thought: “My code is so tightly coupled and I didn’t even think about it for so long. I must do something about it now.”

He also studied detailed steps to implement the Factory Method – Design Pattern and came up with the following steps to implement the same in his Cheesecake project.

  1. First of all, as all Cheesecake flavor classes were similar and had the same set of methods, he decided to create an interface that would have the same set of methods and implement all Cheesecake classes from this interface. This would ensure that new flavor classes have the same set of methods and also simplify implementation.
  2. Then, he needed to create a Factory class that would have all the methods to create objects of all flavor classes
  3. He also had to create a factory Generator class which would create a factory object depending on the input supplied
  4. It was also necessary to create an object of the Factory Generator class in the project file and just call one function.
  5. Done.

Below you can see the code changes that he introduced.

  • He added an interface as it is indicated below.

Unit Name: CheeseCakeInterface.pas

123456789101112131415
unit CheeseCakeInterface;
interface
type
ICheeseCake = Interface
procedure MakeCrust();
procedure AddLayer();
procedure AddFlavor();
procedure Bake();
End;
implementation
end.
  • He changed flavor classes to implement the above-described interface ICheeseCake

E.g. Class TStrawberryCheeseCake

12345678910
type
TStrawberryCheeseCake = class(TInterfacedObject, ICheeseCake)
public
procedure MakeCrust();
procedure AddLayer();
procedure AddFlavor();
procedure Bake();
End;
  • He created a factory class that has methods to create flavor class objects and now the interface depends not on classes but on interfaces.

Unit Name: CheeseCakeFactory.pas

12345678910111213141516171819202122232425262728293031323334353637
unit CheeseCakeFactory;
interface
uses
CheeseCakeInterface;
type
TCheeseCakeFactory = class
public
function CreateStrawberryCheesecake(): ICheeseCake;
function CreateBlueberryCheesecake(): ICheeseCake;
function CreateMangoCheesecake(): ICheeseCake;
end;
implementation
uses
StrawberryCheeseCake,
BlueberryCheeseCake,
MangoCheeseCake;
function TCheeseCakeFactory.CreateBlueberryCheesecake: ICheeseCake;
begin
Result := TBlueberryCheeseCake.Create;
end;
function TCheeseCakeFactory.CreateMangoCheesecake: ICheeseCake;
begin
Result := TMangoCheeseCake.Create;
end;
function TCheeseCakeFactory.CreateStrawberryCheesecake: ICheeseCake;
begin
Result := TStrawberryCheeseCake.Create;
end;
end.
  • He created a factory generator class that has one method which asks for input from a customer, created a factory object, called the factory method to create an object of that flavor class that a customer specifies and called all necessary methods to prepare Cheesecake of that flavor.

Unit Name: CheeseCakeFactoryGenerator.pas

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
unit CheeseCakeFactoryGenerator;
interface
type
TCheeseCakeFlavour = (ckfStrawberry, ckfBlueberry, ckfMango);
procedure PrepareCheeseCake();
implementation
uses
CheeseCakeInterface,
CheeseCakeFactory;
procedure PrepareCheeseCake();
var
Flavor: TCheeseCakeFlavour;
FlavorStr: String;
Cheesecake: ICheeseCake;
CheeseCakeFactory: TCheeseCakeFactory;
begin
Writeln('Which Flavour of Cheesecake do you want?');
Readln(FlavorStr);
Flavor := ckfStrawberry;
Cheesecake := Nil;
if FlavorStr = 'Strawberry' then
Flavor := ckfStrawberry
else if FlavorStr = 'Blueberry' then
Flavor := ckfBlueberry
else if FlavorStr = 'Mango' then
Flavor := ckfMango;
CheeseCakeFactory := TCheeseCakeFactory.Create;
try
case Flavor of
ckfStrawberry: Cheesecake := CheeseCakeFactory.CreateStrawberryCheesecake;
ckfBlueberry: Cheesecake := CheeseCakeFactory.CreateBlueberryCheesecake;
ckfMango: Cheesecake := CheeseCakeFactory.CreateMangoCheesecake;
end;
Cheesecake.MakeCrust;
Cheesecake.AddLayer;
Cheesecake.AddFlavor;
Cheesecake.Bake;
ReadLn;
finally
CheeseCakeFactory.Free;
end;
end;
end.
  • He removed all the complexities from the project file and just called one method “PrepareCheeseCake” from CheeseCakeFactoryGenerator and it was done.

Unit Name: CheeseCakeShop.dpr

1234567891011121314151617
program CheeseCakeShop;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
CheeseCakeFactoryGenerator;
begin
try
CheeseCakeFactoryGenerator.PrepareCheeseCake;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

Well done! There was a lot of code refactoring.

The code looked a little bit more complicated than before. But Bill felt a fraction of satisfaction after implementing all the coding principles he had learned at the college. However, he was still unsure if this was going to help him.

After all this, Bill remembered a discussion with Jake about the new flavor of cheesecake which would be “Coffee”.

He looked at the code again and started to think about changes he would need to make in the new code structure to implement this new flavor. He came up with the steps that are enumerated below.

  1. To create a new flavor class TCoffeeCheeseCake implementing an interface “ICheeseCake”
  2. To create a new method in CheeseCakeFactory to create and return a Coffee flavor class object
  3. To make changes in the PrepareCheeseCake method to call a new method from CheeseCakeFactory

As soon as he introduced the above-mentioned changes to his code, he realized that adding a new flavor became much easier now and no changes to the project file would be required.

Wow! This is amazing!

Now, Bill doesn’t about any new flavors Jake will decide to offer.

Bill has become creative too!!

Pros of the Factory Method Pattern

  1. Tight coupling is avoided by separating concrete implementation and creation
  2. The Single Responsibility Principle is followed by moving object creation code to one place, making the code easier to support
  3. Open/Closed Principle: New classes can be easily introduced without breaking existing implementation

Cons of the Factory Method Pattern

  1. The code may become more complicated as you need to introduce many new classes for the implementation

(But as you progress with the introduction of new classes, you will notice that the process of maintaining and changing classes is much easier than it was before.)

by Vaibhav Ramteke, senior content contributor at Softacom 

Subscribe to our newsletter and get amazing content right in your inbox.

This field is required
This field is required Invalid email address

Thank you for subscribing!
See you soon... in your inbox!

confirm your subscription, make sure to check your promotions/spam folder

Tags

Subscribe to our newsletter and get amazing content right in your inbox.

You can unsubscribe from the newsletter at any time

This field is required
This field is required Invalid email address

You're almost there...

A confirmation was sent to your email

confirm your subscription, make sure to check
your promotions/spam folder