169. Implementing the Factory pattern

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.41.205