TL; DR
Using Supplier provides better readability / maintainability than an explicit constructor call, such as the new MyObject() idiom, handled by switch / if-else if .
It also provides better security , which creates reflection by reflection, an alternative to the new MyObject() idiom, often used before Java 8 to solve maintainability problems, but which presents other problems.
Creating an instance of the Factory class is not necessarily the best example to illustrate the benefits of Supplier .
So, suppose we want to instantiate Shape classes from the Factory class.
A factory of Shape should create an instance of another type of Shape (subclasses of it).
1) Without Supplier and with idiom new MyShape() you will end with a method that contains several if-else if / switch that check the type of criteria / parameters and set the expected class according to this criteria / parameter.
For instance:
public static Shape createShape(String criteria) { if (criteria.equals("circle")){ return new Circle(); } else if (criteria.equals("square")){ return new Square(); } ... }
This is bad, because when you add a class to be processed by a method, you must change this method to a new if-else if / switch so that it takes it into account.
This leads to unsupported code where you can quickly create side effects.
2) To avoid this problem, we often use reflection with Class.newInstance() . It fixes the if-else if / switch problem, but often creates others, since reflection may not work (security issue, class does not work, etc.), and you will only know this at runtime.
It still comes to fragile code.
Here are the reasons prompting Supplier :
By providing Supplier , the check is performed at compile time : if the class is not real, the compiler throws an error.
In addition, a method that uses / accepts a Supplier<Shape> should not use if-else if / switch .
When you use Supplier , you usually come across two cases (of course, not necessarily):
Supplier<Shape> objects are created by the factory class.
For example, we can use factory a Map , where Supplier<Shape> instances are stored, and changing the code to add / remove elements on the map is really cleaner, like adding a new branch to the if-else if / switch , since it is much less verbose and a way to change the map populating (add map.put() or remove map.put() operator) are less likely to create side effects.
Supplier<Shape> objects are created and provided by the customer class. In this case, the factory does not even need to be changed.
So the card is not even required .
And on the client side, while the client provides a valid Supplier<Shape> parameter, this is normal .
Enter safe, supported code: as you can see, these two methods using the Supplier<Shape> address completely eliminate new MyShape() and instantiation on the reflected idiom.
I will give two examples to illustrate these two ways.
An example where a Shape Supplier created in a factory:
public class SimpleShapeFactory { private static Map<String, Supplier<Shape>> shapesByCriteria = new HashMap<>(); static { shapesByCriteria.put("square", Square::new); shapesByCriteria.put("circle", Circle::new); } public static Shape createShape(String criteria) { return shapesByCriteria.get(criteria).get(); } }
The client can call this as follows:
Shape square = SimpleShapeFactory.createShape("square"); Shape circle = SimpleShapeFactory.createShape("circle");
This code will not work at runtime due to the Square or Circle instance, as it is checked at compile time.
And the task of creating instances of Shape is in one place and easily changes:
static { shapesByCriteria.put("square", Square::new); shapesByCriteria.put("circle", Circle::new); }
An example where a Shape Supplier provided by a customer:
public class ComplexShapeFactory { public static Shape composeComplexShape(List<Supplier<Shape>> suppliers) { Shape shape = suppliers.get(0); for (int i = 1; i < suppliers.size() - 1; i++) { shape = shape.compose(suppliers.get(i + 1).get()); } return shape; } }
The client can create complex forms, clinging to the Supplier this way when he calls the method:
Shape squareWithTwoCircles = ComplexShapeFactory.composeComplexShape(Arrays.asList(Square::new, Circle::new, Circle::new));
Validation is performed at compile time, and since the provider is provided by the client, the client can add a new Shape class without changing the factory.