DEV Community

Sota
Sota

Posted on • Edited on

Simple Factory

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;
}
Enter fullscreen mode Exit fullscreen mode

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.
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.

Image description

  1. BurgerShop
    This is our client class. 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.

  2. 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.

  3. Burger
    This abstract class provides common interface among all the burgers and defines default behaviors.

  4. Burger subclasses
    Here are our concrete products. They can implement specific behavior by overriding methods as long as they extends Burger class.

Structure

Image description

Implementation in Java

public enum BurgerType {
    BEEF,
    CHICKEN,
    FISH,
    VEGGIE
}
Enter fullscreen mode Exit fullscreen mode
// 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");
    }
}
Enter fullscreen mode Exit fullscreen mode
// 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);
    }
}
Enter fullscreen mode Exit fullscreen mode
// 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");
    }
}
Enter fullscreen mode Exit fullscreen mode
// 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");
        };
    }
}
Enter fullscreen mode Exit fullscreen mode
// This is our Client
public class BurgerShop {

    public 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;
    }
}
Enter fullscreen mode Exit fullscreen mode
public class BurgerTestDrive {

    public static void main(String[] args) {
        BurgerShop shop  = new BurgerShop();
        Burger burger = shop.orderBurger(BurgerType.VEGGIE);
        System.out.println(burger);
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

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)