简体   繁体   中英

Binding generic service with its implementation and subtypes

Disclaimer: Bear with me for a good amount of code to explain the scenario.

In one of the maven modules (core), we have the following classes:

abstract class ReportingEvent {
}

abstract class Element {

    public <E extends ReportingEvent> Optional<E> getReportEvent() {
        return Optional.empty();
    }

}

services such as:

public interface Reporting<E extends ReportingEvent> {
    void report(E event);
}

interface InternalService {
}

public class InternalServiceImpl implements InternalService {

    @Inject
    Reporting<ReportingEvent> reporting; // 1. Possible to use a generic type? How?

    private void reportEvents(BatchRequest batchRequest) {
        batchRequest.stream()
                // section below is of importance
                .map(m -> m.getEntity().getReportEvent()) // the generic method from 'Element'
                .filter(Optional::isPresent)
                .map(Optional::get)
                .forEach(event -> reporting.report(event)); // method from 'Reporting'
    }
}

class CoreBindingModule extends AbstractModule {
    protected void configure() {
        bind(InternalService.class).to(InternalServiceImpl.class).in(Singleton.class);
    }
}

Further in another maven module(consumer) that we deploy, we have classes related to and implementing the above as:

abstract class BaseReporting extends ReportingEvent {
}

class ColdReporting extends BaseReporting {
}

abstract class Node extends Element {
}

class Cold extends Node {
    @Override
    public Optional<ColdReporting> getReportEvent() {
        return Optional.ofNullable(new ColdReporting()); // some business logic
    }
}

class ReportingImpl implements Reporting<ReportingEvent> { // 2. Use 'BaseReporting' here
    void report(ReportingEvent event){}
}

class ConsumerBindingModule extends AbstractModule {
    protected void configure() {
        bind(new TypeLiteral<Reporting<ReportingEvent>>() {}).to(ReportingImpl.class).in(Singleton.class);
    }
}

The above code works fine. But the problem is the use of types that don't quite relate to the modules.

A... So if I change the binding in the consumer module to

bind(new TypeLiteral<Reporting<BaseReporting>>() {}).to(ReportingImpl.class).in(Singleton.class);

with

class ReportingImpl implements Reporting<BaseReporting> {
    void report(BaseReporting event){}
}

I get an error

No implementation for Reporting<ReportEvent> was bound. while locating Reporting<ReportEvent> for field at InternalServiceImpl.reporting(InternalServiceImpl.java:21)

which is relevant and I cannot make use of Reporting<BaseReporting> in the core module anyway.

B... On the other hand if I try to inject Reporting as:

@Inject
Reporting<? extends ReportingEvent> reporting;

then IDEA states

 Required type: capture of ? Provided: ReportingEvent

on the line

...forEach(event -> reporting.report(event))

Is there a way to get around this situation while trying to solve for 1 and 2 as mentioned in the code sections?

(I might be restating what you already know for the most part)

Keeping aside your Guice related modules and configuration, your problem can be written down as

Reporting<ReportingEvent> reportingEvent = new ReportingImpl();
Reporting<? extends ReportingEvent> reporting = baseReporting;
reporting.report(reportingEvent); //won't work

which is identical to

List<? extends String> list = new ArrayList<>();
list.add("a"); //won't work

Say, you have a HotReporting class that extends BaseReporting , thus you have something like this

public class ReportingImpl implements Reporting<ReportingEvent> { 
    public void report(ReportingEvent event){}
}
public class ColdReportingImpl implements Reporting<ColdReporting> { 
    public void report(ColdReporting event){}
}

public class HotReportingImpl implements Reporting<HotReporting> { 
    public void report(HotReporting event){}
}

Assume if you injected a HotReportingImpl for the field Reporting<? extends ReportingEvent> reporting Reporting<? extends ReportingEvent> reporting .

In your code what if m.getEntity().getReportEvent() returns a ColdReporting ? It is not compatible with what the report method expects here (a HotReporting ).


Option 1:

I would try to get rid of the generic type parameters and define Reporting as

public interface Reporting {
    void report(ReportingEvent event);
}

public class BaseReportingImpl implements Reporting {
    @Override
    public void report(ReportingEvent event) {

    }
}
//... and so on

Problems:

  • If you relied on the exact subtype of a ReportingEvent in the implementation (need typecasts which is not good).
  • Still, a HotReportingImpl can get a ColdReporting object as an argument.

Option 2:

You can make the InternalService generic by adding the type parameters to it.


If the classes implementing Reporting have to deal with concrete types of ReportingEvent , then it doesn't seem right for Element to return the base type ( ReportingEvent ).

Have a look at Typesafe heterogeneous containers by Joshua Bloch . Your problem overlaps with that. You can maintain a mapping from the type of ReportingEvent to the corresponding subclass of Reporting with that type argument.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM