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 co开发者_高级运维mponents. 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) { ... }
It works if you make the type of Component into a type variable for ReportBuilder:
public interface ReportBuilder<T extends Component> {
public Report buildReport(List<T> components);
}
public class PDFReportBuilder implements ReportBuilder<PDFComponent> {
public Report buildReport(List<PDFComponent> components);
}
You'll have to evaluate whether you really want a type variable in ReportBuilder though. It's not always the correct choice. Additionally, if you also want PDFReportBuilder.buildReport
to have a return type that is PDFReport, then you need to have that as a type variable as well (i.e., public interface ReportBuilder<T extends Component, S extends Report>
).
It seems to me like you're setting yourself up for a messy implementation by having three parallel inheritance hierarchies. May I ask, why can't you merge the Component and ReportBuilder's shared behavior? Effectively, you lose any abstraction on the components by forcing the service caller to know the subclass of the Report they want.
I'd suggest simplifying the interface by minimizing or eliminating the parameters to buildReport()
public class ReportService {
ReportComponentRepository repo;
List<ReportBuilder> builders;
public <T extends Report> T getReport(Class<T> reportType) {
// Build report from components using one of the registered builders
for (ReportBuilder builder : builders) {
if (builder.buildFor(reportType) {
//don't pass components - if there's a requirement
//for a strongly typed subclass of Component, just
//let the Report instance figure it out.
return builder.buildReport();
}
}
}
}
//example use
public class PDFReportBuilder implements ReportBuilder {
ComponentSource componentSource;
@Override
public Report buildReport() {
PDFReport report;
for (PDFComponent component : componentSource.getPDFComponents()) {
// assemble report ...
report.includeComponent(component);
// no instanceof operations!
}
return report;
}
}
精彩评论