Design Patterns : Factory and Factory method
Let us dive into individual design patterns, starting with Factory design pattern and then Factory method design pattern. Both these are two most common design patterns used in real-time applications. As mentioned in the previous article – Design Patterns – Basics (artofcoding.dev), they fall under the category - Creational design patterns.
Even experienced engineers have a misconception that both these patterns are the same, and usually use both the names interchangeably. Let us explore further and find what they are, how they differ, when, where and how to use them.
Factory design pattern
What does a factory do? Creates something as per the requirements. That is precisely what a Factory design pattern also does. It tries to solve the fundamental problems in object instantiation by providing abstraction. Gang of Four (GoF) defines factory design pattern as -
A factory is an object which is used for creating other objects.
The implementation will have a factory class with a method which will return different types of objects based on it's input parameters. Here, the object creation logic is hidden from the client class which calls this method.
Real life example
Let us assume that we are asked to develop an application to process data from Excel, Word, and PDF files after reading it.
Here, since all 3 are documents we process, we can have a document class and 3 different subclasses to represent each type of document, i.e. Word, Excel, and PDF.
The user will be providing the file path, and we need to identify the document type and process it accordingly.
Without any pattern
namespace FactoryPattern.Interfaces
{
public interface IDocument
{
public void OpenFile(string filePath);
public void ProcessData();
}
}
We need to define an interface or an abstract class so that we can define all the properties and methods a document should have. I created an interface – IDocument
with 2 methods – OpenFile
and ProcessData
declared.
Now we have to create 3 different classes for each document type.
ExcelDocument.cs:
using FactoryPattern.Interfaces;
namespace FactoryPattern.Classes
{
public class ExcelDocument : IDocument
{
public void OpenFile(string filePath)
{
// File read logic specific to Excel
}
public void ProcessData()
{
// Data processing logic specific to Excel
}
}
}
WordDocument.cs:
using FactoryPattern.Interfaces;
namespace FactoryPattern.Classes
{
public class WordDocument : IDocument
{
public void OpenFile(string filePath)
{
// File read logic specific to Word
}
public void ProcessData()
{
// Data processing logic specific to Word
}
}
}
PdfDocument.cs:
using FactoryPattern.Interfaces;
namespace FactoryPattern.Classes
{
public class PdfDocument : IDocument
{
public void OpenFile(string filePath)
{
// File read logic specific to Pdf
}
public void ProcessData()
{
// Data processing logic specific to Pdf
}
}
}
You can see all 3 classes implements the interface – IDocument
and have their specific implementation of methods – OpenFile
and ProcessData
.
Now let us create a class that tries to open these documents.
Program.cs:
namespace FactoryPattern
{
class Program
{
static void Main(string[] args)
{
// This should ideally come from the UI.
// Hard coded to simulate
string filePath = "c:/document_to_open.pdf";
IDocument document = null;
// Create instance based on file extension
switch (Path.GetExtension(filePath).ToLower())
{
case ".pdf":
document = new PdfDocument();
break;
case ".doc":
case ".docx":
document = new WordDocument();
break;
case ".xls":
case ".xlsx":
document = new ExcelDocument();
break;
default:
document = new WordDocument();
break;
}
document.OpenFile(filePath);
document.ProcessData();
}
}
}
This works fine and achieves the requirements. By default, a Word document is created. We can throw an error if needed to specify – unknown file type.
When executed, it will open and process the file based on the file extension.
But this code posses certain drawbacks. Let us see what they are:
The class
Program
is tightly coupled withPdfDocument
,ExcelDocument
, andWordDocument
.The code is difficult to test.
It is not scalable. If we need to add a new document type, we will have to modify the client code.
Let us see how we can overcome these issues with Factory design pattern.
Implementation using Factory design pattern
With Factory design pattern, we will be shifting the responsibility of creating the document class objects from the Program
class to a Factory
class. This will hide the object creation logic from all the clients.
DocumentFactory.cs:
using FactoryPattern.Interfaces;
namespace FactoryPattern.Classes
{
public class DocumentFactory
{
public IDocument ReadDocument(string filePath)
{
IDocument document = null;
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentNullException(nameof(filePath));
// Other validations to check if the path exists.
switch (Path.GetExtension(filePath).ToLower())
{
case ".pdf":
document = new PdfDocument();
break;
case ".doc":
case ".docx":
document = new WordDocument();
break;
case ".xls":
case ".xlsx":
document = new ExcelDocument();
break;
default:
document = new WordDocument();
break;
}
document.OpenFile(filePath);
return document;
}
}
}
We created a class DocumentFactory
and added a method – ReadDocument
to it, which takes the file path as a parameter.
Then, we can rewrite Program.cs
as follows:
namespace FactoryPattern
{
class Program
{
static void Main(string[] args)
{
// This should ideally come from the UI.
// Hard coded to simulate
string filePath = "c:/document_to_open.pdf";
var documentFactory = new DocumentFactory();
IDocument document = documentFactory.ReadDocument(filePath);
document.ProcessData();
}
}
}
DocumentFactory
takes the responsibility of creating the document, making the design more scalable in future.
This is now testable. We can create a Unit test project and use the following to test.
namespace FactoryPatternTests
{
[TestFixture]
public class DocumentFactoryTests
{
[TestCase("")]
[TestCase(" ")]
[TestCase(null)]
public void ReadDocument_EmptyPath_ThrowsArgumentNullException(string path)
{
// Arrange
var documentFactory = new DocumentFactory();
// Act + Assert
Assert.Throws<ArgumentNullException>(() => documentFactory.ReadDocument(path));
}
[TestCase("c:/document.pdf", typeof(PdfDocument))]
[TestCase("c:/document.doc", typeof(WordDocument))]
[TestCase("c:/document.docx", typeof(WordDocument))]
[TestCase("c:/document.xls", typeof(ExcelDocument))]
[TestCase("c:/document.xlsx", typeof(ExcelDocument))]
[TestCase("c:/document.default", typeof(WordDocument))]
public void ReadDocument_ValidPath_ReturnsExpectedDocument(string path, Type expectedDocumentType)
{
// Arrange
var documentFactory = new DocumentFactory();
// Act
var document = documentFactory.ReadDocument(path);
// Assert
Assert.IsNotNull(document);
Assert.IsInstanceOf(expectedDocumentType, document);
}
}
}
Method ReadDocument_ValidPath_ReturnsExpectedDocument
ensures that we get the right IDocument
object.
Class design
When to use Factory design pattern
Factory design pattern could usually be used when
Objects have common functionality and can be extended to subclasses
Client need not be concerned with the exact subclass to be created.
Implementation classes are subject to change or if it requires allowing new implementations in future.
Factory method design pattern
The key difference this pattern has with Factory design pattern is that it allows the subclass to decide which class is to be instantiated. To achieve this, an abstract class acts as the Factory class and return the instance of the document.
Let us see how this applies to our above example and solves the problem.
We have the interface – IDocument
and 3 classes that implements the interface.
Then we will need to create the DocumentFactory
. In Factory design pattern, this was a concrete class. Instead, we will create an abstract class in Factory method.
public abstract class DocumentFactory
{
protected abstract IDocument ReadDocument();
public IDocument LoadDocument()
{
return this.ReadDocument();
}
}
The Factory contains an abstract method – ReadDocument
and a concrete method – LoadDocument
, which internally calls the ReadDocument
method of the subclass. This subclass creates the document object and returns it. The responsibility of creating the document now lies on the implementations of DocumentFactory
.
We will need to create 3 more classes here.
ExcelDocumentFactory.cs:
public class ExcelDocumentFactory : DocumentFactory
{
protected override IDocument ReadDocument()
{
IDocument document = new ExcelDocument();
return document;
}
}
PdfDocumentFactory.cs:
public class PdfDocumentFactory : DocumentFactory
{
protected override IDocument ReadDocument()
{
IDocument document = new PdfDocument();
return document;
}
}
WordDocumentFactory.cs:
public class WordDocumentFactory : DocumentFactory
{
protected override IDocument ReadDocument()
{
IDocument document = new PdfDocument();
return document;
}
}
We now have an abstract document factory class and 3 implementations for it, which returns the document object.
Now, the client code can create a document as follows:
public class Program
{
static void Main(string[] args)
{
// This should ideally come from the UI.
// Hard coded to simulate
string filePath = "c:/document_to_open.pdf";
var documentFactory = new ExcelDocumentFactory();
IDocument document = documentFactory.LoadDocument();
document.OpenFile(filePath);
document.ProcessData();
}
}
At this point, you must be wondering – How we will decide which document factory to use. If we are not sure of which, It is recommended to use Factory Design pattern.
Class design
When to use Factory method design pattern
Factory method design pattern could usually be used for
Objects have common functionality and can be extended to subclasses
The client knows which subclass to create, but do not want to get hands dirty by actually creating the subclass object.
Implementation classes are subject to change or if it requires allowing new implementations in future.
Another real life situation where you need to use factory method pattern would be as follows:
The requirement is to create 3 forms to gather information from employees. The first one is to gather their personal data, second one to gather their experience details and the third one to enter their bank details.
We need to have a common base class – IForm
, and 3 implementations – PersonalForm
, ExperienceForm
, and FinanceForm
. Also create an abstract class - FormFactory
and its implementations – PersonalFormFactory
, ExperienceFormFactory
, and FinanceFormFactory
.
In the application, whenever we have to gather finance information, we can use the FinanceFormFactory
.
I hope this article helps you in some way to understand Factory pattern and Factory method pattern. Your support and love in the form of comments and feedback are highly appreciated. Please don't hesitate to ask questions if any. Thanks.
Appendix
The source code used in this article could be found here - Design-patterns/Creational/FactoryPattern : github
Shout-outs and courtesy notes
Image by pch.vector on Freepik
Subscribe to my newsletter
Read articles from Febin Joy directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Febin Joy
Febin Joy
A software engineer with over 20 years of experience in designing and developing solutions for various domains and industries with expertise in OOD, SOLID principles, Software Architecture, and Design patterns.