2015-04-04 8 views
8

Ich arbeite an einem Projekt, bei dem Dienste zu einer Komponente hinzugefügt werden müssen. Die Service Klasse ist eine Schnittstelle ohne Methoden. Hier ist ein Beispiel, wie meine Dienste funktionieren:Generics-Platzhalter mit "extends" und "super"

public interface Service { } 

public interface CarWash extends Service { 
    void washCar(Car car); 
} 

public interface CarRepair extends Service { 
    void repairCar(Car car); 
} 

Jetzt gibt es viele Implementierungen dieser Dienste. Eine einzelne Klasse kann mehrere Dienste, wie diese Garage Klasse implementieren:

public class Garage implements CarWash, CarRepair { 
    @Override 
    public void washCar(Car car) { /* .. */ } 
    @Override 
    public void repairCar(Car car) { /* .. */ } 
} 

Wenn ein Dienst für eine Komponente hinzufügen, ich will nicht den Service für alle Aufgaben zu verwenden, um brauchen, aber beispielsweise verwenden, um die Garage nur für Autos waschen (CarWash), aber nicht zur Reparatur (CarRepair). Deshalb habe ich die Aufgaben als Klassen angeben, wie folgt aus:

void addService(Service service, Class<? extends Service> task); 

Um zu überprüfen, ob der Dienst tatsächlich die Aufgabe durchführen konnte, habe ich Generika:

<T extends Service> addService(T service, Class<? super T> task); 

Das funktioniert gut, aber nicht überprüft, ob die vorausgesetzte Aufgabe ist eigentlich eine Aufgabe (eine Klasse, die Service implementiert), so dass dies funktionieren würde:

addService(myTask, Object.class); 

ich nach einem Weg suchen, um spec ify dass service Bedürfnisse zu implementieren (verlängern) die task und die task erweitert die Service Schnittstelle, wie diese (nicht kompiliert):

<T extends Service> addService(T service, Class<? super T extends Service> task); 
+1

Wie wäre es mit: ' void addService (S-Service, Klasse clazz);'? –

Antwort

7

Ich denke, dass <T extends Service, S extends T> void addService(S service, Class<T> clazz) klingt wie es Ihre Kriterien erfüllt:

public static class Foo {                     
    public interface Service { }                   

    public interface CarWash extends Service {                
    void washCar();                      
    }                          

    public interface CarRepair extends Service {               
    void repairCar();                     
    }                          

    static <T extends Service, S extends T> void addService(S service, Class<T> clazz) {}     

    public static void main(String[] args) {                
    addService(null, CarWash.class); // Fine.                 
    addService(null, Object.class); // Compilation error.               
    } 
} 

(ich habe noch einige Statik und entfernt Car von Methodensignaturen, da ich keine Definition von denen haben zu kompilieren)

+0

Funktioniert, vielen Dank! Das einzige Problem, das ich habe, ist die Varargs, die alle Array-Elemente vom gleichen Typ zwingen, und deshalb kann ich 'addService (null, CarWash.class, CarRepair.class);' nicht schreiben. – Frithjof

+0

Nun, Sie haben nicht nach Varargs gefragt ... das klingt nach einem Fall, in dem Sie sowieso nicht wirklich ein Array verwenden wollen, und sollten die Verwendung eines Iterable bevorzugen, z. 'static void addService (S Dienst, Iterable > clazzes)'. Aber ich denke nicht, dass das in Bezug auf "Service", das all diese Klassen implementiert, das Richtige tun würde. Ich denke in diesem Fall wäre es besser, explizite Überladungen mit 1, 2, 3 usw. Klassenparametern zu definieren. Klingt chaotisch. –

+0

Ich stimme zu. Ich denke, es ist in Ordnung, es als einen Parameter zu belassen und die Methode mehrmals aufzurufen, um einen Dienst für mehrere Aufgaben hinzuzufügen, so wie Sie es in Ihrer Antwort haben. – Frithjof

1

Dies auch in Ordnung sein kann, je nachdem, wie Sie die Art des service verwenden:

<T extends Service> void addService(T service, Class<T> task) 

Wenn die service ein Subtyp des Typs von task vertreten ist, dann kann es immer upcasted werden.

addService(new Garage(), CarWash.class); // OK, Garage is a CarWash 

Das einzige Problem, das ich habe, ist die varargs, der alle Array-Elemente zwingen, von der gleichen Art zu sein, und deshalb kann ich nicht addService(null, CarWash.class, CarRepair.class);

Dies ist tatsächlich eine schwierige schreiben Problem für Java-Generics. (C++ kann dies mit variadic templates tun, was ein Feature ist, das Java sehr unwahrscheinlich ist.)

Also eine Möglichkeit, die Sie in Java lösen können, ist die Laufzeitvalidierung, z.:

<T extends Service> void addService(
     T service, Class<? super T> tasks...) { 
    for(Class<? super T> task : tasks) 
     if(!Service.class.isAssignableFrom(tasks)) 
      throw new IllegalArgumentException(); 
} 

(. Oder Class<? extends Service> verwenden und prüfen, ob task.isInstance(service))

Aber ich weiß, was wir tun wirklich nicht so. ;

)

Java hat etwas namens ein intersection type (wo, wenn wir einen Typ <? extends T & U> haben, die T & U Teil eine Kreuzung Typ) genannt wird, aber ein Schnitttyp nicht super und extends und sie sind ansonsten ziemlich begrenzt, was sonst kombinieren können sie tun können.