What is the Factory Method Pattern?
The factory method pattern allows you to create objects (or instantiate classes) without having to specify the exact class that is being created. Say for instance you have a service class that can be called by multiple object types and those object types each have their own object-specific implementation for creating tasks for them. Instead of writing a ton of if else's in the service class to determine which class should be constructed, you could leverage the factory method pattern and call a class that holds only the code relevant to that object. This allows you to appropriately separate your code out and make it easier to maintain, as well as make it less likely that you inadvertently alter the functionality of the code that should be run for the other objects.
Why is it Useful?
It's useful because if used appropriately it can allow for much more dynamic and flexible implementations in your codebase. The amount of flexibility when used appropriately can be absolutely astounding. Let's take a look at two different examples. One not using the factory pattern and another that does!
Creating Tasks for Different Objects (No Factory Pattern):
public with sharing class Task_Service_Impl
{
//This method calls the task creators for each object type
public void createTasks(Set<Id> recordIds, Schema.SObjectType objectType)
{
if(objectType == Account.getSObjectType()){
//Accounts (and the other object types below) is not the same as the regular Account object.
//This is further explained in the domain layer chapter of this guide. Basically you name your domain class
//the plural version of the object the domain represents
new Accounts().createTasks(recordIds);
}
else if(objectType == Case.getSObjectType()){
new Cases().createTasks(recordIds);
}
else if(objectType == Opportunity.getSObjectType()){
new Opportunities().createTasks(recordIds);
}
else if(objectType == Taco__c.getSObjectType()){
new Tacos().createTasks(recordIds);
}
else if(objectType == Chocolate__c.getSObjectType()){
new Chocolates().createTasks(recordIds);
}
//etc etc for each object could go on for decades
}
}
Creating Tasks for Different Objects (Factory Pattern):
//The task service that anywhere can call and it will operate as expected with super minimal logic
public with sharing class Task_Service_Impl implements Task_Service_Interface
{
//This method calls the task creators for each object type
public void createTasks(Set<Id> recordIds)
{
//Using our Application class we are able to instantiate new instances of domain classes based on the recordIds we pass
//the newInstance method.
//We cover the fflib_Application class and how it uses the factory pattern a ton more in the next section.
fflib_ISObjectDomain objectDomain = Application.domain.newInstance(recordIds);
if(objectDomain instanceof Task_Creator_Interface){
Task_Creator_Interface taskCreator = (Task_Creator_Interface)objectDomain;
taskCreator.createTasks(recordIds);
}
}
}
Right now this might look very confusing. You might be asking, "How on Earth is this possible??", "How does that code even operate?", or "How can so much code be reduced to so little?". The first thing we do is instantiate a new domain class using our Application class (our factory class) simply by sending it record ids. The Application factory class generates the object-specific Domain class by determining the object type of the set of recordIds using the Id.getSObjectType() method that Salesforce makes available in Apex. Then by implementing the Task_Creator_Interface interface on each of the objects' domain classes we are guaranteeing that if something is an instance of the Task_Creator_Interface it will have a method called createTasks! Depending on the use case this can take hundreds of lines of if else logic and reduce it to almost nothing. It also helps in the Separation of Concerns area by making our code more confined to one specific area/concern. It delegates logic more to their respective services or domains instead of somewhere the logic probably doesn't belong.
Where does it fit into Separation of Concerns?
It reduces your need to declare concrete class/object types in your code in many places and it allows you to create extremely flexible and abstract services (more on this in the implementing the service layer with apex common library section). Again take the example of task creation, maybe you have 15 controller classes (classes connected to a UI) in your org making tasks for 15 different objects and each object has a different task implementation, but you want to move all that task creation logic into a singular service class that anywhere can call for any object at any time. The factory method pattern is quite literally built for this scenario. In fact, there is an example of exactly that below, using a simple factory class to create tasks. Chapter 04 then builds the same idea on the fflib_Application class.
Where is it used in the Apex Common Library?
It's leveraged heavily by the fflib_Application class, which you can find out more about here.
Example Code (Abstract Task Creation App)
The following code example in the repo is an example of how the factory pattern could work in a real-world Salesforce implementation to allow for tasks to be created on multiple objects using a different implementation for each object.