igr
igr

Reputation: 10604

Java8 Supplier that is an optional Consumer

I can explain this one only with the example.

We have the main class, AppServer. It holds also several application-wide components.

AppServer app = new AppServer(config, ...);

Now we need to provide a supplier that will act as a factory for some Foo instances. This supplier will be called several times in the loop to create some amount of the Foo instances. By using the supplier, we allow users to provide their own implementation of the Foo. Note that Foo is NOT our class and we can NOT change it.

The only problem is that Foo requires some of the components from the application. They need to be injected/provided to the FooImpl.

This can be written like this:

app.setFooSupplier(() -> new FooImpl(app.component()));

This is kind of ugly for me and wonder if there is a better way to do this? Here are some ideas so far...

(1) Inject dependencies after supplier is used (IoC way).

Dependencies are defined with the setters. So we have something like (inside of AppServer), in sudo:

Foo foo = fooSupplier.get();
maybeInject(foo, component1);
maybeInject(foo, component2);
...

What component is injected depends if the setter is there. Alternatively, we can have Component1Aware interfaces and do the same like:

Foo foo = fooSupplier.get();
if (foo instanceof Component1Aware) {
    ((Component1Aware)foo).setComponent1(component1);
}
...

which is basically the same.

I would like to have dependencies in constructor, so to express they need to be set.

(2) Use optional Consumer

Create a Supplier instance for FooImpl that is in the same time Consumer of the AppServer. Something like:

public class FooImplSupplier implements Supplier<Foo>, Consumer<AppServer> {
    ...
}

and then we can register this supplier very easily:

app.setFooSupplier(new FooImplSupplier());

and after the supplier creates an instance (in AppServer) we do the following:

Foo foo = fooSupplier.get();
if (foo instanceof Consumer) {
    ((Consumer)foo).accept(this);
}

Wdyt?

Upvotes: 0

Views: 940

Answers (2)

Holger
Holger

Reputation: 298123

It is not unusual to provide context information to a supplier. The problem you have is that you are fixed on the Supplier interface which distracts you from the simple solution. Instead of trying to combine the Supplier with a Consumer you should use a Function:

void setFooProvider(Function<AppServer,Foo> f) {
  this.fooProvider=Objects.requireNonNull(f);
}
// …
// in a method within the same class:
    Foo foo=fooProvider.apply(this);

Caller:

app.setFooProvider(appArg -> new FooImpl(appArg.component()));

I changed the method’s name from …Supplier to …Provider to make clear that the role of the provided argument is not tight to a particular interface. You may use Supplier, Function, BiFunction, or a custom interface, whatever fits.

Now the specified provider uses it’s argument and doesn’t capture values from its surrounding context which makes it invariant (and with the current implementation it will be a singleton).

Note that it is still possible to implement provider functions which simply ignore the argument so the original functionality of the Supplier based solution is not lost.

Upvotes: 2

Peter Lawrey
Peter Lawrey

Reputation: 533482

I always favour passing mandatory settings via the constructor (or a static factory method) The IoC style I prefer is

app.setFooSupplier(() -> new FooImpl(app.component()));

or you could write the following if app.component() returns the same thing each time.

Component comp = app.component();
app.setFooSupplier(() -> new FooImpl(comp));

This is by far the simplest and the hardest to get wrong. e.g. you can't pass an argument 0 or multiple times, or try to use the Supplier before it is initialised.

Upvotes: 2

Related Questions