Is a java-8 provider to replace constructor call

I looked at the Java 8 Supplier and Consumer interfaces and, as I understand it, it can replace the constructor call.
I saw an example on dzone ( link here ) where ShapeFactory is used. The code is pretty simple, simple factory for a form class.
But she uses it like this:

Supplier<ShapeFactory> supplier = ShapeFactory::new; supplier.get().getShape("rectangle").draw(); 

But why is it better than a simple classic:

 ShapeFactory factory = new ShapeFactory(); factory.getShape("rectangle").draw() 

It is much simpler and more efficient. In addition, if the constructor of the ShapeFactory class has an argument, the provider will not work, and we will have to use a function or other methods.

So why use a Provider like this in this particular case?

+5
source share
4 answers

In the above example, it makes no sense to complicate the work of the supplier. A simple constructor call is even easier to read (and most likely will be faster). The author of this example probably intended to demonstrate how Supplier can be called.

Providers become useful when you need to retrieve an extraction (not necessarily creation) of an object, such as a "strategy template." For instance:

 public void drawRectangle(final Supplier<ShapeFactory> factorySupplier) { final ShapeFactory factory = factorySupplier.get(); factory.getShape("rectangle").draw(); } 

Then you can call your method as follows and it will create a new factory:

 drawRectangle(ShapeFactory::new); 

or, for example, as follows:

 @Autowired private ShapeFactory shapeFactoryBean; ... drawRectangle(() -> shapeFactoryBean); 

and instead of the existing factory, he will use the existing factory.


Of course, the method from my example can just take a ShapeFactory as an argument. But you can imagine a situation in which, for example, a factory should be available only in certain situations, so there is no need to create a new factory call to call the method, for example:

 public void drawRectangleConditionally(final Supplier<ShapeFactory> factorySupplier) { if (something) { final ShapeFactory factory = factorySupplier.get(); factory.getShape("rectangle").draw(); } } ... drawRectangleConditionally(ShapeFactory::new); // the factory will only be created if it really needed. 

Note that a provider can be used multiple times, so another use for suppliers is to obtain a sequence of objects, for example:

 public List<T> createList(final int n, final Supplier<T> listItemSupplier) { final List<T> result = new ArrayList<>(n); for (int i = 0; i < n; ++i) { result.add(listItemSupplier.get()); } return result; } ... createList(5, MyObject::new); // creates a list of 5 MyObjects createList(3, () -> "hello"); // creates a list of 3 "hello" strings createList(10, Random::nextLong); // creates a list of 10 random longs 
+5
source

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.

+6
source

Many times a person makes a lot of effort to learn new technology. Then they began to actively invest in new technology and begin to use it mainly by any other approach.

There is a variant of the old adage, "When you have a hammer, the work ever looks like a nail."

I saw streams that were used when a simple loop would be easier to read and maintain, due to an attempt to embed fluctuating and switching logic into the stream. I saw for loops that were simple enough that they read better as threads.

In my opinion, this person has learned Streams very well, well enough to bring the constructive paradigm into its flows in a thread-like manner. I do not think it was a wise choice, but I would have guessed that if you ask them about it, their answer might be "go to the study of threads."

+3
source

I do not consider this a very good example of a factory pattern. I would suggest something like

 // "old code" interface ShapeFactory { Shape create(); } class CircleFactory implements ShapeFactory { Shape create() { return new Circle(); } } class CircleFactory implements ShapeFactory { Shape create() { return new Circle(); } } class ShapeDrawer { ShapeFactory factory; public void setFactory(ShapeFactory f) { factory = f; } public void draw() { factory.create().draw(); } } // called with shapeDrawer.setFactory(new CircleFactory()); shapeDrawer.draw(); shapeDrawer.setFactory(new RectangleFactory()); shapeDrawer.draw(); // with Supplier class ShapeDrawer { Supplier<Shape> factory; public void setFactory(Supplier<Shape> f) { factory = f; } public void draw() { factory.get().draw(); } } // called like this shapeFactory.setFactory(Circle::new); shapeDrawer.draw(); shapeFactory.setFactory(Rectangle::new); shapeDrawer.draw(); 

Thus, there is much less template code around using the factory template.

+1
source

Source: https://habr.com/ru/post/1270491/


All Articles