2010-09-12 5 views
0

Je suis look pour de bonnes alternatives à l'invocation d'une interface spécifique à partir d'un cadre générique. Je vais illustrer avec le code. Regardez la partie question, l'exemple de code est principalement inclus pour la rigueur, et pour mettre l'exemple dans un scénario réel.Java interfaces génériques avec les implémentations Typesafe

Exemple

Supposons que nous voulons construire un rapport basé sur une liste des composants. Disons que nous avons deux types de composants spécifiques:

public interface Component { ... } 
public class PDFComponents extends Component { ... } 
public class WordComponents extends Component { ... } 

Chaque composant a une implémentation ReportBuilder, par exemple,

public interface ReportBuilder { ... } 
public class PDFReportBuilder extends ReportBuilder { ... } 
public class WordReportBuilder extends ReportBuilder { ... } 

qui construit des implémentations de rapport spécifiques

public interface Report { ... } 
public class PDFReport extends ReportBuilder { ... } 
public class WordReport extends ReportBuilder { ... } 

Enfin, nous avons le service qui localise composants et produit un rapport sur les composants.

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

Exemple d'utilisation du service

List<PDFReport> reports = new ReportService().getReport(PDFReport.class); 

Question

maintenant à la question. Comment puis-je concevoir une interface générique ReportBuilder qui permette la sécurité des types dans ses implémentations?

Par exemple, le choix de l'interface:

public Report buildReport(List<? extends Component> components); 

causerait la laideur dans sa mise en oeuvre:

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

quand nous désirons vraiment l'interface pour PDFReportBuilder être par exemple,

public Report buildReport(List<PDFComponent> component) { ... } 
+0

Je suppose que vous souhaitez implémenter des interfaces au lieu de les étendre, le '' PDFReport' met en œuvre VERBAL D', et parenthèse après les classes/interfaces les noms ne sont pas vraiment là. –

+0

Vous avez absolument raison.À la longueur de mon message, je vois que j'étais négligent avec certains détails. Ceci est corrigé maintenant. –

Répondre

2

Cela fonctionne si vous définissez le type de composant dans une variable de type pour 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); 
} 

Vous devrez évaluer si vous voulez vraiment une variable de type dans ReportBuilder. Ce n'est pas toujours le bon choix. De plus, si vous voulez aussi PDFReportBuilder.buildReport d'avoir un type de retour qui est PDFReport, alors vous devez avoir que comme une variable de type aussi bien (à savoir public interface ReportBuilder<T extends Component, S extends Report>).

+0

Spot sur. Parfois, j'oublie les principes fondamentaux des génériques tout en appliquant certaines des techniques les plus avancées. Je suis d'accord que c'est le plus puissant quand un paramètre T et un type de retour S sont spécifiés. –

+0

Votre déclaration sur le type de retour n'est pas vraie. Une méthode de substitution (ou d'implémentation) est autorisée à spécifier un type de retour plus spécifique depuis Java 1.5. – ILMTitan

2

Il me semble que vous vous mettre en place pour une mise en œuvre désordonnée en ayant trois hiérarchies d'héritage parallèles. Puis-je demander, pourquoi ne pouvez-vous pas fusionner le comportement partagé de Component et ReportBuilder? En effet, vous perdez toute abstraction sur les composants en forçant l'appelant à connaître la sous-classe du rapport qu'il souhaite.

Je vous suggère de simplifier l'interface en minimisant ou en éliminant les paramètres à 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; 
    } 
} 
+0

Quelle excellente idée, c'est certainement une excellente approche pour minimiser la complexité de la logique applicative. –