Java 8 供应商是否替换构造函数调用



我一直在研究Java 8的供应商和消费者接口,以及我所理解的它可以取代构造函数调用的内容。
我在dzone上看到了一个使用ShapeFactory的例子(链接在这里)。 代码非常简单,是形状类的简单工厂,
但她是这样使用它的:

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

但是为什么它比做一个简单的经典更好:

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

这要简单得多,也效率更高。此外,如果 ShapeFactory 类的构造函数有参数,则 Supplier 将不起作用,我们将不得不使用 Function 或其他方法。

那么,为什么要在这种精确的情况下使用这样的供应商呢?

TLDR

与显式构造函数调用(例如由switch/if-else if语句处理的new MyObject()习语)相比,使用Supplier带来更好的可读性/可维护性
它还带来了更好的类型安全性,即通过反射实例化,这是new MyObject()习语的替代方案,通常在Java 8之前用于解决可维护性问题,但这会带来其他问题。


实例化Factory类不一定是说明Supplier的好处的最佳示例。
因此,假设我们要从Factory类实例化Shape类。

Shape工厂应该实例化不同类型的Shape(它的子类)。

1)没有Suppliernew MyShape()成语,您将完成一个包含一些if-else if/switch语句的方法,该方法检查一种标准/参数并根据该标准/参数实例化预期的类。
例如:

public static Shape createShape(String criteria) {
if (criteria.equals("circle")){
return new Circle();
}
else if (criteria.equals("square")){
return new Square();
}
...
}  

这很糟糕,因为当您添加一个类以通过该方法处理时,您必须使用新的if-else if/switch更改此方法,以便它考虑在内。
它会导致代码不可维护,您可能会快速产生副作用。

2)为了避免这个问题,我们经常使用反射和Class.newInstance()。 它消除了if-else if/switch问题,但它经常创建其他问题,因为反射可能不起作用(安全问题、类不可实例化等),并且您只会在运行时知道它。
它仍然导致一个脆弱的代码。

以下是支持Supplier的原因:

通过提供Supplier检查在编译时执行:如果类不可实例化,编译器将发出错误。
此外,使用/接受Supplier<Shape>的方法不需要使用if-else if/switch语句。

当你使用Supplier时,你通常会遇到两种情况(当然不是相互排斥的):

  • Supplier<Shape>对象由工厂类实例化。
    例如,我们可以在Map工厂中使用存储Supplier<Shape>实例和修改代码以在映射中添加/删除元素确实更干净,因为向if-else if/switch语句添加新分支,因为它不那么冗长,并且更改映射填充(添加map.put()或删除map.put()语句)的方法不太容易产生副作用。

  • Supplier<Shape>对象由客户端类实例化和提供。 在这种情况下,工厂甚至不需要改变。
    所以地图是这样甚至不需要的。
    从客户端来看,虽然客户端提供了有效的Supplier<Shape>参数,但这很好

类型安全,可维护的代码:您可能会注意到,这两种使用Supplier<Shape>的方式完全解决了反射习语new MyShape()和实例化的缺点。


我将举两个例子来说明这两种方式。


在工厂中创建Shape Supplier的示例:

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();
}           
}

客户端可以这样调用它:

Shape square = SimpleShapeFactory.createShape("square");
Shape circle = SimpleShapeFactory.createShape("circle");

此代码在运行时不会因为SquareCircle的实例化而在运行时失败,因为它是在编译时检查的。
实例化Shape实例的任务位于同一位置且易于更改:

static {
shapesByCriteria.put("square", Square::new);
shapesByCriteria.put("circle", Circle::new);
}

客户端提供Shape Supplier的示例:

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;
}    
}

客户端可以通过在调用该方法时以这种方式链接Supplier来创建复杂的形状:

Shape squareWithTwoCircles = ComplexShapeFactory.composeComplexShape(Arrays.asList(Square::new, Circle::new, Circle::new));

检查仍然在编译时完成,由于供应商由客户提供,客户端可以在不更改工厂的情况下添加新的Shape类。

在上面的例子中,使供应商的事情复杂化是没有意义的。 简单的构造函数调用甚至更容易阅读(并且可能更快)。可能,本考试的作者打算演示如何调用Supplier

当您需要提取对象的检索(不一定是创建)时,供应商变得很有用,这是一种"策略模式"。 例如:

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

然后,你可以像这样调用你的方法,它将创建一个新工厂:

drawRectangle(ShapeFactory::new); 

或者像这样:

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

它将使用现有工厂而不是创建新工厂。


当然,我示例中的方法可以只将ShapeFactory作为参数。但是可以想象这样一种情况,例如,工厂只需要在某些情况下访问,因此不需要为方法调用创建一个新工厂,例如:

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's really needed. 

请注意,供应商可以多次使用,因此供应商的另一个用途是获取对象序列,例如:

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

很多时候,一个人付出了很多努力来学习新技术。 然后,他们对新技术投入了大量资金,并开始优先使用它而不是任何其他方法。

有一句老话的变体"当你有锤子时,工作看起来像钉子"。

我见过当一个简单的 for 循环更容易阅读和维护时使用的 Streams,因为试图在流中嵌入 flitering 和类似开关的逻辑。 我见过足够简单的循环,它们作为流读起来更好。

据我估计,这个人对Streams的了解非常好,足以以类似Stream的方式将构造范式融入到他们的Streams中。 我不认为这是一个明智的选择,但我猜如果你问他们,他们的回答可能是"去学习流"。

我不认为这是工厂模式的一个很好的例子。 我会建议类似

// "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();

因此,围绕工厂模式的使用,样板代码要少得多。