Skip to main content

Command Palette

Search for a command to run...

Ways to Use the Factory Pattern for Simplified Object Creation in Java

This in-depth guide explores how the Factory Pattern simplifies complex object creation in Java. Aimed at advanced developers, it dissects the problem of direct instantiation, explains the theory behind the Factory Method pattern, and walks throu...

Published
7 min read
Ways to Use the Factory Pattern for Simplified Object Creation in Java
T

I am Tuanh.net. As of 2024, I have accumulated 8 years of experience in backend programming. I am delighted to connect and share my knowledge with everyone.

1. The Factory Pattern Concept

In modern Java development, repeatedly using new to create objects can scatter construction logic throughout the code and make future changes hard. The Factory Pattern centralizes object creation in one place. As Wikipedia explains, this pattern “uses factory methods to deal with the problem of creating objects without having to specify their exact classes”. In practice, a factory defines a common interface or static method to build objects, and subclasses or helper classes decide which concrete type to instantiate. For example, a ShapeFactory might implement a createShape(String type) method: clients call the factory without knowing if they get a Circle, a Rectangle, or another class. This indirection means client code only works with abstract types (Shape), while the factory handles all new calls. The result is loose coupling – the client is independent of concrete classes – and greater flexibility: new product types can be added by extending the factory, without rewriting client logic

1.1 The Problem of Direct Object Creation

Direct object creation can quickly become unwieldy in large systems. Consider code that needs different subclasses at runtime (e.g. based on configuration or user input). Without a factory, a client might have many if/else blocks or switch statements littered across the codebase:

if(type.equals("circle")) new Circle();
else if(type.equals("rectangle")) new Rectangle();

This approach scatters creation logic. It exposes the new operator everywhere and tightly couples code to specific classes. As DigitalOcean notes, the Factory Pattern “takes out the responsibility of the instantiation of a class from the client program to the factory class”. In other words, a specialized factory class or method absorbs the messy branching. The client simply asks the factory for an object and uses the returned interface type. This solves the rigidity problem: if new subclasses are needed later, you only update the factory, not every client site.

1.2 Definition of Factory Pattern

Formally, the Factory Pattern is a creational design pattern that provides an interface for creating objects, while letting subclasses or separate factory classes decide which concrete class to instantiate. In the classic “Gang of Four” description, it “defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses”. Key roles include: a Product interface (e.g. Shape), one or more Concrete Products (Circle, Square), and a Creator/Factory with a factory method (could be an abstract class with an abstract method, or a class with a static method). The creator relies on this method to return a Product. Subclasses of the creator override the method to return different products. As Wikipedia’s UML description notes, the creator “does not instantiate the concrete class directly. Instead, it refers to a separate factoryMethod() to create a product object, which makes the creator independent of the exact concrete class that is instantiated”. In essence, the Factory Pattern hides the new operator inside factory methods, so clients remain unaware of concrete types.

1.3 Benefits of the Factory Pattern

Using the Factory Pattern brings several advantages. Encapsulation of creation means that clients “don’t know how objects are created”. They just call the factory and get an abstract reference back. This leads to loose coupling: client code depends only on interfaces or base classes, not specific classes. Another benefit is scalability: introducing a new product type is easy. You create the new class and update the factory logic (or add a new factory subclass) without modifying existing clients. GeeksforGeeks summarizes: factories “support easy addition of new product types without modifying existing code”. The pattern also promotes reuse of creation code: common setup belongs in one factory location rather than repeated everywhere. Finally, factories can enhance testability: for example, unit tests can use a mock factory to supply stub objects. In short, the Factory Pattern adheres to the open–closed principle: the system can extend new products (open) without changing existing client code (closed). The cross-platform dialog example illustrates this well: by delegating button creation to a factory method, the base dialog code doesn’t change when a new OS-specific button is introduced.

1.4 Real-world Example: UI Factory Method

To visualize how a factory can centralize creation, consider a UI framework example. The base Dialog class defines a createButton() factory method and never directly calls new WindowsButton() or new HTMLButton(). Instead, each concrete subclass (like WindowsDialog or WebDialog) overrides createButton() to return a platform-specific Button. As RefactoringGuru explains, “if we declare a factory method that produces buttons inside the base Dialog class, we can later create a subclass that returns Windows-styled buttons from the factory method”. The base class’s render() method then calls createButton().render(). Because Dialog only works with the abstract Button interface, the exact button type is irrelevant to the dialog logic. In this way the factory method pattern decouples Dialog from concrete buttons: new button types can be added simply by subclassing Dialog and overriding the factory method, without changing the rendering code. This makes the code more flexible and adherent to the open-closed principle.

2. Implementing the Factory Pattern in Java

2.1 Example Java Code

To see the Factory Pattern in action, here is a simple Java example using shapes. We define a Shape interface and concrete classes, then use a factory to create them:

// Product interface
interface Shape {
void draw();
}

// Concrete Products
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}

class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a square");
}
}

// Factory class with factory method
class ShapeFactory {
// Static factory method
public static Shape createShape(String type) {
if (type.equalsIgnoreCase("circle")) {
return new Circle();
} else if (type.equalsIgnoreCase("square")) {
return new Square();
} else {
throw new IllegalArgumentException("Unknown shape type: " + type);
}
}
}

// Client code
public class Main {
public static void main(String[] args) {
Shape s1 = ShapeFactory.createShape("circle");
s1.draw(); // prints "Drawing a circle"

Shape s2 = ShapeFactory.createShape("square");
s2.draw(); // prints "Drawing a square"
}
}

In this example, the ShapeFactory.createShape(String) method handles all object creation.

2.2 Explanation of the Code

In the code above, ShapeFactory encapsulates the new logic. The static method createShape(type) checks the input string and returns a corresponding Shape instance. Notice the client code (in Main) never uses new Circle() or new Square(). Instead, it simply calls ShapeFactory.createShape("circle") and works with the returned Shape interface. This matches the idea from tutorials: “we create object without exposing the creation logic to the client”. The client’s responsibility is reduced to saying what it wants (a circle or square), not how it’s constructed.

This design has clear benefits. First, if you later add a new shape (say Triangle), you only need to implement Triangle implements Shape and extend the factory’s createShape (add another else if branch). The rest of the code remains untouched. GeeksforGeeks highlights this: Factory methods make it easy to introduce new product types without altering existing clients. Second, the client code is decoupled from concrete classes: it only uses the Shape interface. As a result, it “reduces dependency between client and concrete classes”. Third, all instantiation logic lives in one place (the factory). This centralization simplifies maintenance and testing; for example, one could substitute a mock ShapeFactory in tests.

Finally, this pattern aligns with well-known Java practices. The DigitalOcean tutorial notes that factories are widely used (e.g. DocumentBuilderFactory for XML parsers). By following this approach in our example, we ensure that object creation is consistent and flexible. The client code (here, Main) never needs to be changed when creation details evolve; it simply invokes ShapeFactory and trusts the interface.

If you have any questions about using the Factory Pattern or want to share your own examples, please leave a comment below!

Read more at : Ways to Use the Factory Pattern for Simplified Object Creation in Java

More from this blog

T

tuanh.net

540 posts

Are you ready to elevate your Java, OOP, Spring, and DevOps skills? Look no further!