Factory Method Design Pattern in Java

Introduction

Design patterns are a collection of programming methodologies used in day-to-day programming. They represent solutions to some commonly occurring problems in the programming industry, which have intuitive solutions.

Sooner or later, a desktop program, mobile app, or some other type of software will inevitably become complex and start exhibiting certain kinds of problems. These problems are typically related to the complexity of our codebase, non-modularity, inability to separate certain parts from each other, etc.

For this reason, design patterns have become the de facto standard in the programming industry since their initial use a few decades ago due to their ability to solve many of these problems. In this article, we will dive deeper into one of these methodologies - namely, the Factory Method Pattern.

Creational Design Patterns

The Factory Method Pattern is one of several Creational Design Patterns we often use in Java. Their purpose is to make the process of creating objects simpler, more modular, and more scalable.

These patterns control the way we define and design the objects, as well as how we instantiate them. Some encapsulate the creation logic away from users and handles creation (Factory and Abstract Factory), some focus on the process of building the objects themselves (Builder), some minimize the cost of creation (Prototype) and some control the number of instances on the whole JVM (Singleton).

Specifically, the Factory Method and Abstract Factory are very common in Java software development.

The Factory Method Pattern

The Factory Method Pattern (also known as the Virtual Constructor or Factory Template Pattern) is a creational design pattern used in object-oriented languages.

The main idea is to define an interface or abstract class (a factory) for creating objects. Though, instead of instantiating the object, the instantiation is left to its subclasses.

Each object is created through a factory method available in the factory - which can either be an interface or an abstract class.

If the factory is an interface - the subclasses must define their own factory methods for creating objects because there is no default implementation.

If the factory is a class - the subclasses can use existing implementation or optionally override factory methods.

With the Factory Pattern, the object creation logic is hidden from the client. Instead of knowing the exact object class and instantiating it through a constructor, the responsibility of creating an object is moved away from the client.

The client can then create objects through a common interface which simplifies the process.

This approach separates the object creation from the implementation, which promotes loose coupling and thus easier maintenance and upgrades.

Motivation

After some theoretical introduction, let's see the Factory Pattern in practice.

Imagine we're trying to build our own spaceship. Since this is a simplified example, we'll also simplify the construction and say that our spaceship consists of a hull, an Engine, and a satellite Dish:

public class SpaceshipHangar {
    public Spaceship createSpaceship() {
        Spaceship ship = new Spaceship();
        Engine engine = new SublightEngine();
        Dish dish = new RoundDish();

        ship.setEngine(engine);
        ship.setDish(dish);

        return ship;
    }
}

Note: SublightEngine and RoundDish are subclasses of Engine and Dish, respectively.

Now imagine that you showed your new spaceship to a friend, and suddenly they want a spaceship of their own too. But instead of the SublightEngine they want to put a HyperdriveEngine, and instead of the RoundDish they want to put a SquareDish:

public class SpaceshipHangar {
    public Spaceship createSpaceship() {
        Spaceship ship = new Spaceship();
        Engine engine = new HyperdriveEngine();
        Dish dish = new SquareDish();

        ship.setEngine(engine);
        ship.setDish(dish);

        return ship;
    }
}

Since the instantiations are hard-coded, you could either create a duplicate of your original method or change its code.

If you duplicate the method each time someone else wants to make a slight modification to the ship, this quickly becomes a problem because you will have lots of almost identical methods with minimal difference.

If you change the original code, then the method itself looses the point because it needs to be rewritten every time someone wants to make a small change to the ship.

This goes on as you add more related variations of a logical collection - all spaceships for instance.

Implementation

To solve this problem, we can create a Factory of spaceships and leave the specifics (which engine or dish is used) to the subclasses to define.

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

Instead of hard-coding object creation into the createSpaceship() method with new operators, we'll create a Spaceship interface and implement it through a couple of different concrete classes.

Then, using a SpaceshipFactory as our communication point with these, we'll instantiate objects of Spaceship type, though, implemented as concrete classes. This logic will stay hidden from the end user, as we'll be specifying which implementation we want through the argument passed to the SpaceshipFactory method used for instantiation.

Let's start off with the Spaceship interface:

public interface Spaceship {
    void setEngine(Engine engine);
    void setDish(Dish dish);
}

Since we're working with the Engine and Dish classes, let's define them real quick:

public class Engine {
    private String model;

    public Engine(String model) {
        this.model = model;
    }

    // Getters and Setters
}

public class Dish {
    private String model;

    public Dish(String model) {
        this.model = model;
    }

    // Getters and Setters
}

And now, let's implement the interface through two concrete implementations, starting with the SpaceshipMk1:

public class SpaceshipMk1 implements Spaceship {
    private Engine engine;
    private Dish dish;

    public SpaceshipMk1(Engine engine, Dish dish) {
        this.engine = engine;
        System.out.println("Powering up the Mk.1 Raptor Engine");

        this.dish = dish;
        System.out.println("Activating the Mk.1 Satellite Dish");
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setDish(Dish dish) {
        this.dish = dish;
    }
}

And the SpaceshipMk2:

public class SpaceshipMk2 implements Spaceship {
    private Engine engine;
    private Dish dish;

    public SpaceshipMk2(Engine engine, Dish dish) {
        this.engine = engine;
        System.out.println("Powering up the Mk.2 Raptor Engine");

        this.dish = dish;
        System.out.println("Activating the Mk.2 Satellite Dish");
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setDish(Dish dish) {
        this.dish = dish;
    }
}

Now, instead of simply instantiating these as we typically would, let's create a SpaceshipFactory for them:

public class SpaceshipFactory {
    public Spaceship getSpaceship(Engine engine, Dish dish) {
        if (engine.getModel().equals("Mk.2") && dish.getModel().equals("Mk.2")) {
            return new SpaceshipMk2(engine, dish);
        } else if (engine.getModel().equals("Mk.1") && dish.getModel().equals("Mk.1")) {
            return new SpaceshipMk1(engine, dish);
        } else {
            System.out.println("Incompatible models of engine and satellite dish.");
        }
        return null;
    }
}

The factory typically has a single method called getTypeName() with the parameters you'd wish to pass. Then, through as many if statements required, we check which exact class should be used to serve the call.

And with this factory in place, when we'd like to instantiate any of these two spaceship classes, we use the factory:

SpaceshipFactory factory = new SpaceshipFactory();

Engine engineMk1 = new Engine("Mk.1");
Dish dishMk1 = new Dish("Mk.1");

Engine engineMk2 = new Engine("Mk.2");
Dish dishMk2 = new Dish("Mk.2");

Spaceship spaceshipMk1 = factory.getSpaceship(engineMk1, dishMk1);
Spaceship spaceshipMk2 = factory.getSpaceship(engineMk2, dishMk2);
Spaceship spaceshipMkHybrid = factory.getSpaceship(engineMk1, dishMk2);

Here, instead of using the new operator to instantiate either spaceship, we call upon the common interface Spaceship and using the factory construct/instantiate the objects. Running this code would yield:

Powering up the Mk.1 Raptor Engine
Activating the Mk.1 Satellite Dish
Powering up the Mk.2 Raptor Engine
Activating the Mk.2 Satellite Dish
Incompatible models of engine and satellite dish.

Note: Ideally, we'd also have factories for engines and dishes, especially if we have derived types such as HyperdriveEngine and SquareDish. Having multiple factories would end up with multiple new keywords - which is against what the Factory Method stands for.

What is the fix then? Haven't we only made a roundabout and ended up with the same problem?

That's where the Abstract Factory Design Pattern jumps in. It's like a factory of factories which would, using the same approach, instantiate all spaceship-related factories with only a single new call at the start.

Pros and Cons

Pros

  • Allows loosely-coupled code, making changes less disruptive
  • Easy to unit test and mock as code is decoupled

Cons

  • Makes code less readable since all object creation code is behind a layer of abstraction
  • If used with the Abstract Factory Pattern (a factory of factories), the code quickly becomes cumbersome but functional

Conclusion

The Factory Method and other design patterns are tested and proven-to-work techniques. Regardless if used in personal projects or very large industry codebases. They offer clever solutions to some common problems and encourage developers and entire teams to do architecture design first, programming second. This almost always leads to a higher-quality code rather than jumping right into programming.

It's a misconception that design patterns are holy solutions to all problems. Design patterns are techniques to help mitigate some common problems, invented by people who have solved these problems numerous times.

Last Updated: March 2nd, 2020
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms