Factory Pattern: Replacing the Conditional Statement with Lambda Expressions

The “Factory Pattern” falls under the creational design patterns and is most widely used in Java applications. Similar to the other patterns, this pattern helps us write organized and clean code.

But, for me, the conditional statement in the “Factory Pattern” presents a possible breach of the clean code principles, especially as the number of conditions increases.

In this post, I will share a technique for replacing the conditional statement with a map of lambda calls in Java.

Definition

It is not the purpose of this post to explain the “Factory Pattern” or how it works in detail. Thus, I am referring to the famous book “Design Patterns: Elements of Reusable Object-Oriented Software” to quote the pattern’s intent:

Intent:
Define and interface for creating an object, but let the subclasses decide which class to instantiate. Factory method lets class defer instantiation to subclasses.

Use Case

For this post, I am assuming that we have an interface called “Car,” 4 sub-classes (DieselCar, ElectricalCar, HybridCar, and PetrolCar), and one Factory class, “CarFactory.” The diagram for those classes would be as follows:

Traditional Implementation

The traditional implementation of this pattern requires us to have either an if-else or a switch statement in the method CarFactory.getCar().

So, we end up with one of the below implementations:

The IF-ELSE Block

public class CarFactoryIfStatement {

private CarFactoryIfStatement() {
}

static Car getCar(String type, String brand) {
if (type.equals("Diesel")) {
return new DieselCar(brand);
} else if (type.equals("Electrical")) {
return new ElectricalCar(brand);
} else if (type.equals("Hybrid")) {
return new HybridCar(brand);
} else if (type.equals("Petrol")) {
return new PetrolCar(brand);
}
throw new UnsupportedOperationException();
}
}

The Switch Block

public class CarFactorySwitchStatement {

private CarFactorySwitchStatement() {
}

static Car getCar(String type, String brand) {
switch (type) {
case "Diesel":
return new DieselCar(brand);
case "Electrical":
return new ElectricalCar(brand);
case "Hybrid":
return new HybridCar(brand);
case "Petrol":
return new PetrolCar(brand);
default:
throw new UnsupportedOperationException();
}
}
}

The above code snippets might not look very bad, but as we add more care types, the code will become messy, and sonar will complain!

So, what can we do about this code?

Lambda to the Rescue

As a replacement, I will use a HashMap where the Key is of type String, and the Value is an interface “CarCreator.”

First, we write the interface CarCreator that has only a single method, ‘initialize‘ that takes a String and return the Car instance (as shown below):

private interface CarCreator {
Car initialize(String brand);
}

Second, we initialize our map ‘CAR_FACTORY_MAP.’ Our value in this map is a lambda expression that initializes the correct Car instance corresponding to the key.

private static final Map<String, CarCreator> CAR_FACTORY_MAP = ImmutableMap.of(
"Diesel", brand -> new DieselCar(brand),
"Electrical", brand -> new ElectricalCar(brand),
"Hybrid", brand -> new HybridCar(brand),
"Petrol", brand -> new PetrolCar(brand)
);

Note: I am using the Guava library from Google to ease the initialization of the map.

Finally, we implement the method ‘CarFactory.getCar.‘ Instead of the huge bulk of the code we wrote previously with If-Else and Switch blocks, we will call the initialize method of the ‘CarCreator‘ interface.

static Car getCar(String type, String brand) {
if (CAR_FACTORY_MAP.containsKey(type)) {
return CAR_FACTORY_MAP.get(type).initialize(brand);
}
throw new UnsupportedOperationException();
}

Following those three steps, the code of the ‘CarFactory‘ class would look something like this:

public class CarFactory {
private CarFactory() {
}
private static final Map<String, CarCreator> CAR_FACTORY_MAP = ImmutableMap.of(
"Diesel", brand -> new DieselCar(brand),
"Electrical", brand -> new ElectricalCar(brand),
"Hybrid", brand -> new HybridCar(brand),
"Petrol", brand -> new PetrolCar(brand)
);

static Car getCar(String type, String brand) {
if (CAR_FACTORY_MAP.containsKey(type)) {
return CAR_FACTORY_MAP.get(type).initialize(brand);
}
throw new UnsupportedOperationException();
}

private interface CarCreator {
Car initialize(String brand);
}
}

Note: I have uploaded the full code to a repository on my Github account.

References

  1. Design Patterns: Elements of Reusable Object-Oriented Software
  2. Tutorials Point – Factory Pattern
  3. [Featured Image] Pixabay – Wheel Valve Heating Line Turn
  4. Source Code on Github