2015-04-04 9 views
8

Pracuję nad projektem, w którym usługi muszą zostać dodane do komponentu. Klasa Service jest interfejsem bez żadnych metod. Oto przykład działania moich usług:Generics wildcarding z "extends" i "super"

public interface Service { } 

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

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

Obecnie istnieje wiele wdrożeń tych usług. Jedna klasa może realizować wiele usług, jak do tej klasy garaż:

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

Po dodaniu usługi do komponentu, nie chcą muszą korzystać z usługi do wszystkich zadań, ale na przykład użyć Garage tylko dla mycie samochodów (CarWash), ale nie do ich naprawy (CarRepair). Dlatego określenie zadań jak klas, podobnie jak to:

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

Aby sprawdzić, czy usługa może rzeczywiście wykonać zadanie, użyłem rodzajowych:

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

to działa dobrze, ale nie sprawdza, czy pod warunkiem zadaniem jest rzeczywiście zadanie (klasa, która implementuje Service), więc to będzie działać:

addService(myTask, Object.class); 

szukam sposobu na SPEC IFY że service musi wdrożyć (przedłużenia) task ORAZ task rozszerza interfejs Service, takiego (nie kompiluje):

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

Co powiedzie się na to: ' void addService (usługa S, klasa clazz);'? –

Odpowiedz

7

myślę że <T extends Service, S extends T> void addService(S service, Class<T> clazz) brzmi jak spełnia on kryteria:

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

(dodałam kilka statykę i usunięte Car z podpisów metoda, ponieważ nie mam definicji tych, do kompilacji)

+0

Działa, dziękuję bardzo! Jedyny problem, jaki mam, to warianty, które wymuszają na wszystkich elementach tablicy tego samego typu i dlatego nie mogę napisać 'addService (null, CarWash.class, CarRepair.class);'. – Frithjof

+0

Teraz nie pytałeś o varargs ... to brzmi jak przypadek, w którym tak naprawdę nie chcesz używać tablicy i wolisz używać Iterable, np. 'static void addService (S service, Iterable > clazzes)'. Ale nie sądzę, że zrobiłoby to coś dobrego w odniesieniu do "usługi" wdrażającej wszystkie te klasy. Myślę, że w takim przypadku lepiej zdefiniowałbyś jawne przeciążenia z 1, 2, 3 itd. Parametrami klasy. Brzmi nieporządnie. –

+0

Zgadzam się. Myślę, że dobrze jest zostawić go jako jeden parametr i kilkakrotnie wywołać metodę, aby dodać usługę dla wielu zadań, tak jak masz ją w swojej odpowiedzi. – Frithjof

1

ten może również być w porządku, w zależności od sposobu korzystania typ service:

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

Jeśli service jest podtypem typu reprezentowanego przez task, to zawsze można upcasted.

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

Jedyny problem mam jest varargs które zmuszają wszystkich elementów tablicy, aby być tego samego typu, a więc nie mogę pisać addService(null, CarWash.class, CarRepair.class);

To jest rzeczywiście trudne problem dla generycznych Java. (C++ może to zrobić z variadic templates, co jest cechą, której mało prawdopodobne jest uzyskanie Java.)

Jednym ze sposobów rozwiązania tego problemu w Javie jest walidacja czasu wykonywania, np.:

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

(. Albo użyć Class<? extends Service> i sprawdzić, task.isInstance(service))

Ale wiem, naprawdę nie tak. ;)

Java ma coś nazywaną intersection type (gdzie jeśli mamy typ <? extends T & U> The T & U część nazywa się typ skrzyżowanie), ale typ przecięcia nie można łączyć super i extends i są inaczej bardzo ograniczony, co jeszcze może zrobić.