The factory design pattern is a creational design pattern, that provides an interface for creating objects, but allows subclasses to alter the type of objects that will be created.
Rather than calling a constructor directly to create an object, we use factory method to create that object based on some input or condition.
I know…I know…didn’t make much sense.
Let’s understand this with the help of an example.
Let’s say you own an ice-cream delivery store where you deliver chocolate ice-cream, strawberry ice-cream and mango ice-cream. So, we have a concrete class for each kind of ice-cream.
// Abstraction for icecream
Interface Icecream {
void scoop();
void pack();
void deliver();
}
// Concrete class for chocolate icecream
class ChocolateIcecream implements Icecream {
public void scoop() {
System.out.println("Scooping chocolate ice cream");
}
public void pack() {
System.out.println("Packing chocolate ice cream");
}
public void deliver() {
System.out.println("Delivering chocolate ice cream");
}
}
// Concrete class for strawberry icecream
class StrawberryIcecream implements Icecream {
public void scoop() {
System.out.println("Scooping strawberry ice cream");
}
public void pack() {
System.out.println("Packing strawberry ice cream");
}
public void deliver() {
System.out.println("Delivering strawberry ice cream");
}
}
// Concrete class for mango icecream
class MangoIcecream implements Icecream {
public void scoop() {
System.out.println("Scooping mango ice cream");
}
public void pack() {
System.out.println("Packing mango ice cream");
}
public void deliver() {
System.out.println("Delivering mango ice cream");
}
} Great!! now we have concrete implementations for each type of ice-cream🍦.
Now, if we had only one kind of ice-cream and we wanted to provide the feature to order ice-cream, our code would have looked like:
Icecream orderIcecream() {
Icecream icecream = new Icecream();
icecream.scoop();
icecream.pack();
icecream.deliver();
}But as we already know, we have more than one kind of ice-cream.
So normally, we would write like:
// Notice that we are passing the type of icecream as a parameter
Icecream orderIcecream(String icecreamType) {
Icecream icecream;
// Instantiate correct concrete class based on the type of icecream
if(icecreamType.equals("chocolate")) {
icecream = new ChocolateIcecream();
} else if(icecreamType.equals("strawberry")) {
icecream = new StrawberryIcecream();
} else if(icecreamType.equals("mango")) {
icecream = new MangoIcecream();
}
icecream.scoop();
icecream.pack();
icecream.deliver();
return icecream;
}But now, whenever you want to add a new kind of ice-cream to the menu or remove some older kind, you would have to modify the orderIcecream method.
This method is NOT closed for modification and hence does not follow open-close principle.
So…what can we do?
Simple Factory
Let’s outsource the responsibility of creating the required ice-cream, to a separate class.
public class SimpleIcecreamFactory {
public Icecream createIcecream(String icecreamType) {
Icecream icecream = null;
if(icecreamType.equals("chocolate")) {
icecream = new ChocolateIcecream();
} else if(icecreamType.equals("strawberry")) {
icecream = new StrawberryIcecream();
} else if(icecreamType.equals("mango")) {
icecream = new MangoIcecream();
}
return icecream;
}
}Now, our ice-cream store can just use the simple factory to create ice-cream.
public class IcecreamStore {
SimpleIcecreamFactory factory;
public IcecreamStore(SimpleIcecreamFactory factory) {
this.factory = factory;
}
public IceCream orderIcecream(String icecreamType) {
Icecream icecream;
icecream = factory.createIcecream(icecreamType);
icecream.scoop();
icecream.pack();
icecream.deliver();
return icecream;
}
}The simple provides a single method to create and return instances of different classes based on input parameters. It centralizes object creation logic in one place, without exposing the instantiation logic to the client.
The simple factory is not actually a design pattern, it’s more of a programming idiom.