I'm look for good alternatives to invoking a specific interface from a generic framework. I'll exemplify with code. Look to the question part, the example code is primarily included for thoroughness, and for putting the example into a real scenario.
Example
Assume we want to build a report based on a list of components. Say we have two specific component types:
public interface Component { ... }
public class PDFComponents extends Component { ... }
public class WordComponents extends Component { ... }
Each component has a ReportBuilder implementation, e.g.,
public interface ReportBuilder { ... }
public class PDFReportBuilder extends ReportBuilder { ... }
public class WordReportBuilder extends ReportBuilder { ... }
Which builds specific report implementations
public interface Report { ... }
public class PDFReport extends ReportBuilder { ... }
public class WordReport extends ReportBuilder { ... }
Finally we have the service which locates components and produces a report out of the components.
public class ReportService {
ReportComponentRepository repo;
List<ReportBuilder> builders;
public <T extends Report> T getReport(Class<T> reportType) {
// Get report components. E.g., this might return List<PDFComponent>
List<Component> reportComponents = repo.getReportComponents(id);
// Build report from components using one of the registered builders
for (ReportBuilder builder : builders) {
if (builder.buildFor(reportType) {
return builder.buildReport(report);
}
}
}
}
Example using the service
List<PDFReport> reports = new ReportService().getReport(PDFReport.class);
Question
Now to the question. How can I design a generic ReportBuilder interface which allows typesafety to it's implementations?
E.g., choosing the interface:
public Report buildReport(List<? extends Component> components);
would cause ugliness in it's implementations:
public class PDFReportBuilder implements ReportBuilder {
@Override
public Report buildReport(List<? extends Component> components) {
PDFReport report;
for (Component component : components) {
if (component instanceOf PDFComponent) {
// assemble report ...
report.includeComponent(component);
}
}
return report;
}
}
when we really desire the interface for PDFReportBuilder to be e.g.,
public Report buildReport(List<PDFComponent> component) { ... }