Factory method is a creational design pattern, it helps to create objects as per client's needs. This can be done by using a constructor function with the new
keyword but there are situations when we have different types of objects and to reduce complexity we want a single method to instantiate the object as per client's requirement. It helps us provide a generic interface where we can specify the type of object we want to create and thus with reduced complexity.
For example we want to create a button object but we have a total of three different types of buttons built for different operating systems, we will want the client to use a generalized method to create the button object to save him from the hassle of selecting the right constructor and then instantiate object.
Another great example I found online: To me the factory pattern is like a hiring agency. You've got someone that will need a variable number of workers. This person may know some info they need in the people they hire, but that's it.
So, when they need a new employee, they call the hiring agency and tell them what they need. Now, to actually hire someone, you need to know a lot of stuff - benefits, eligibility verification, etc. But the person hiring doesn't need to know any of this - the hiring agency handles all of that.
In the same way, using a Factory allows the consumer to create new objects without having to know the details of how they're created, or what their dependencies are - they only have to give the information about what they actually want. courtesy
Structure
Implementation
Factory design pattern is used to make very complex objects but for explaination I have chosen an easy example of bank accounts objects creation.
// Constructor for defining new CurrentAccounts
function CurrentAccount(options){
// default options
this.minBalance = options.minBalance || 1000;
this.interest = options.interest || 0 ;
}
// Constructor for defining new SavingAccounts
function SavingAccount(options){
this.minBalance = options.minBalance || 5000;
this.interest = options.interest || 0.1;
}
// Define a skeleton accountFactory
function AccountFactory(){};
// Define the prototypes and utilities for this factory
// Our default accountClass is CurrentAccount
AccountFactory.prototype.AccountClass = CurrentAccount;
// Our Factory method for creating new Account instances
AccountFactory.prototype.createAccount = function(options = {}){
switch(options.accountType){
case 'current':
this.AccountClass = CurrentAccount;
break;
case 'saving':
this.AccountClass = SavingAccount;
break;
// defaults to CurrentAccount
}
return new this.AccountClass(options);
}
function run(){
const accountMaker = new AccountFactory();
const account1 = accountMaker.createAccount();
const account2 = accountMaker.createAccount({
accountType : 'current',
minBalance : 2000,
interest : 0,
});
const account3 = accountMaker.createAccount({
accountType : 'saving',
minBalance : 20000,
});
console.log(account1);
console.log(account2);
console.log(account3);
}
run();
// out puts:
//CurrentAccount { minBalance: 1000, interest: 0 }
//CurrentAccount { minBalance: 2000, interest: 0 }
//SavingAccount { minBalance: 20000, interest: 0.1 }
We made two constructors CurrentAccount
and SavingAccount
accepting options
object as an arguments, note that both can have some different properties too and it will work fine.
We then created an empty AccountFactory
function and then set its default account constructor to be CurrentAccount
.
Then we made a creator function which is a factory method, it makes the object a per client's requirements and if no options are given default Current account will be created.
When to use factory method
- When object creation is very complex.
- When we want to create different instances of same kind sharing similar properties with lesser complexity for the client.
- When you depend on an external resource but you don't know exactly which one yet.
- When constructing a new instance depends on what instances have already been constructed.
Advantages
- Since object construction is expensive it helps us bulid once and reuse many times.
- Helps have a single point of control for multiple products.
- It makes extensibility very easy.
- Straight forward testability.
Disadvantages
- High number of required classes.
- Extension of the application is very elaborate.