In a nutshell, the Factory pattern allows us to create several kinds of objects without exposing the instantiation process to the caller. This way, we can hide the complex and/or sensitive process of creating objects and expose an intuitive and easy-to-use factory of objects to the caller.
In a classic implementation, the Factory pattern relies on an intern switch(), as shown in the following example:
public static Fruit newInstance(Class<?> clazz) {
switch (clazz.getSimpleName()) {
case "Gac":
return new Gac();
case "Hemi":
return new Hemi();
case "Cantaloupe":
return new Cantaloupe();
default:
throw new IllegalArgumentException(
"Invalid clazz argument: " + clazz);
}
}
Here, Gac, Hemi, and Cantaloupe are implementing the same Fruit interface and have an empty constructor. If this method lives in a utility class named MelonFactory, we can call it as follows:
Gac gac = (Gac) MelonFactory.newInstance(Gac.class);
However, Java 8 functional-style allows us to refer to constructors using the method references technique. This means that we can define a Supplier<Fruit> to refer to the Gac empty constructor, as follows:
Supplier<Fruit> gac = Gac::new;
How about Hemi, Cantaloupe, and so on? Well, we can simply put all of them in a Map (notice that no melon type is instantiated here; these are just lazy method references):
private static final Map<String, Supplier<Fruit>> MELONS
= Map.of("Gac", Gac::new, "Hemi", Hemi::new,
"Cantaloupe", Cantaloupe::new);
Furthermore, we can rewrite the newInstance() method to use this map:
public static Fruit newInstance(Class<?> clazz) {
Supplier<Fruit> supplier = MELONS.get(clazz.getSimpleName());
if (supplier == null) {
throw new IllegalArgumentException(
"Invalid clazz argument: " + clazz);
}
return supplier.get();
}
The caller code doesn't need any further modifications:
Gac gac = (Gac) MelonFactory.newInstance(Gac.class);
However, obviously, constructors are not always empty. For example, the following Melon class exposes a single constructor with three arguments:
public class Melon implements Fruit {
private final String type;
private final int weight;
private final String color;
public Melon(String type, int weight, String color) {
this.type = type;
this.weight = weight;
this.color = color;
}
}
Creating an instance of this class cannot be obtained via an empty constructor. But if we define a functional interface that supports three arguments and a return, then we are back on track:
@FunctionalInterface
public interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
This time, the following statement will try to fetch a constructor with three arguments of the String, Integer, and String types:
private static final
TriFunction<String, Integer, String, Melon> MELON = Melon::new;
The newInstance() method, which was made especially for the Melon class is:
public static Fruit newInstance(
String name, int weight, String color) {
return MELON.apply(name, weight, name);
}
A Melon instance can be created as follows:
Melon melon = (Melon) MelonFactory.newInstance("Gac", 2000, "red");
Done! Now, we have a factory of Melon via functional interfaces.