What is Simple Factory?
Simple factory is not design pattern. It simply decouples object creation from client code. In other words, Simple factory encapsulates object instantiation by moving instantiation logic to a separate class.
Simple factory is often confused with Factory Pattern. We're going to study Simple factory to clarify their difference. Also, learning Simple factory helps us to understand Factory pattern easily.
When to use it?
Programming to concrete implementation should be avoided because it makes an application very difficult to maintain. It is always preferable to program to interface. If you're instantiating a concrete class in client code, then Simple factory comes in handy as Simple factory can decouple object creation from client. This makes our application more extensible and maintainable.
Problem
We are developing system for Burger shop. The system needs to create various burgers such as beef burger, chicken burger and so on.
Our first attempt would be like this:
// Client orders a burger
Burger orderBurger(String type) {
Burger burger;
if (type.equals("beef")) {
burger = new BeefBurger();
} else if (type.equals("chicken")) {
burger = new ChickenBurger();
} else if (type.equals("fish")) {
burger = new FishBurger();
}
burger.prepareBun();
burger.grillPatty();
burger.addToppings();
burger.wrap();
return burger;
}
The problem is, we are coding to implementation not to interface. Where? We use if statement and instantiate a concrete class based on a burger type.
Why is it the problem? Our client code is tightly coupled with object creation, leading less flexibility!! Let's say if we don't sell fish burgers anymore, and start selling veggie burgers. We need to visit our client code and modify it. That is to say, it is not closed for modification.
Solution
To solve the problem, we can create separate class which will be responsible only for object creation. Then our client code doesn't need to worry about object creation and be able to depend on abstraction. This technic is known as "Encapsulate what varies". We expect the code about instantiating concrete objects will be changed frequently, while prepareBun()
, grillPatty()
, addToppings()
, wrap()
processes are likely to stay the same among all the burgers in the future.
The advantage of Simple factory is that it is reusable by other classes. We might have other client classes such as BurgerRestaurant
, BurgerCateringShop
which will use SimpleBurgerFactory.createBurger()
method.
Client
Client instantiates specific burger object through SimpleBurgerFactory. Notice from client perspective, we don't know which concrete burger will be created, that is, object creation logic is now decoupled from client.SimpleBurgerFactory
This class encapsulates what varies which is in this case, object creation logic!createBurger()
is declared as static method because client wants to use this class to instantiate object (of course we can't have an instance before instantiating it!).createBurger()
accepts BurgerType enum to determine which type of burger should be created.Burger
This abstract class provides common interface among all the burgers and defines default behaviors.Burger subclasses
Here are our concrete products. They can implement specific behavior by overriding methods as long as they extends Burger class.
Structure
Implementation in Java
public enum BurgerType {
BEEF,
CHICKEN,
FISH,
VEGGIE
}
// Abstract Product
public abstract class Burger {
public BurgerType burgerType;
public List<String> toppings = new ArrayList<>();
public void prepareBun() {
System.out.println("Preparing a bun");
}
public void grillPatty() {
if (burgerType == null) {
throw new IllegalStateException("pattyType is undefined");
}
System.out.println("Grill a " + burgerType + " patty");
}
public void addToppings() {
for (String item : toppings) {
System.out.println("Add " + item);
}
}
public void wrap() {
System.out.println("Wrap a burger up");
}
}
// Concrete product
public class BeefBurger extends Burger {
public BeefBurger() {
burgerType = BurgerType.BEEF;
List<String> items = List.of("lettuce", "pickle slices", "tomato slice", "BBQ sauce");
toppings.addAll(items);
}
}
// Concrete product
public class VeggieBurger extends Burger {
public VeggieBurger() {
burgerType = BurgerType.VEGGIE;
List<String> items = List.of("smoked paprika", "garlic chips", "crushed walnuts", "veggie sauce");
toppings.addAll(items);
}
// Concrete product can implement specific behavior that differs from other products
@Override
public void wrap() {
System.out.println("Wrapping paper shouldn't print any meats but vegetables");
}
}
// Simple factory, responsible for instantiating an object
public class SimpleBurgerFactory {
public static Burger createBurger(BurgerType type) {
return switch (type) {
case BEEF -> new BeefBurger();
case CHICKEN -> new ChickenBurger();
case FISH -> new FishBurger();
case VEGGIE -> new VeggieBurger();
default -> throw new IllegalArgumentException("unknown burger type");
};
}
}
public class Client {
public static void main(String[] args) {
Burger burger = orderBurger(BurgerType.VEGGIE);
System.out.println(burger); // Check if the object is actually veggie burger
}
public static Burger orderBurger(BurgerType type) {
// Factory is responsible for object creation
Burger burger = SimpleBurgerFactory.createBurger(type);
burger.prepareBun();
burger.grillPatty();
burger.addToppings();
burger.wrap();
return burger;
}
}
Output:
Preparing a bun
Grill a VEGGIE patty
Add smoked paprika
Add garlic chips
Add crushed walnuts
Add veggie sauce
Wrapping paper shouldn't print any meats but vegetables
com.factories.simpleFactory.VeggieBurger@9807454
Pitfalls
- Decision-making code for object instantiation can be more complex sometime. In such a case, we might as well consider using Factory method instead.
Comparison with Factory Pattern
- In Simple factory, there is typically one factory class to decide which type of product to create, while Factory pattern may introduce multiple factories.
- Simple factory often uses static method to create objects, which makes it easy to call but hard to extend. On the other hand, Factory method uses abstract method in super class, which acts as interface for all the factories and subclasses will provide concrete implementation for object instantiation.
You can check all the design pattern implementations here.
GitHub Repository
P.S.
I'm new to write tech blog, if you have advice to improve my writing, or have any confusing point, please leave a comment!
Thank you for reading :)
Top comments (0)