2010-06-12 17 views
6

Moje pytanie nie jest łatwe do wyjaśnienia za pomocą słów, na szczęście nie jest to zbyt trudne do wykazania. Tak więc pokrywa się ze mną:Jak prawidłowo połączyć generykę i dziedziczenie, aby uzyskać pożądany rezultat?

public interface Command<R> 
{ 
    public R execute();//parameter R is the type of object that will be returned as the result of the execution of this command 
} 

public abstract class BasicCommand<R> implements Command<R> 
{ 
} 

public interface CommandProcessor<C extends Command<?>> 
{ 
    public <R> R process(C<R> command);//this is my question... it's illegal to do, but you understand the idea behind it, right? 
} 

//constrain BasicCommandProcessor to commands that subclass BasicCommand 
public class BasicCommandProcessor<C extends BasicCommand<?>> implements CommandProcessor<C> 
{ 
    //here, only subclasses of BasicCommand should be allowed as arguments but these 
    //BasicCommand object should be parameterized by R, like so: BasicCommand<R> 
    //so the method signature should really be 
    // public <R> R process(BasicCommand<R> command) 
    //which would break the inheritance if the interface's method signature was instead: 
    // public <R> R process(Command<R> command); 
    //I really hope this fully illustrates my conundrum 
    public <R> R process(C<R> command) 
    { 
     return command.execute(); 
    } 
} 

public class CommandContext 
{ 
    public static void main(String... args) 
    { 
     BasicCommandProcessor<BasicCommand<?>> bcp = new BasicCommandProcessor<BasicCommand<?>>(); 
     String textResult = bcp.execute(new BasicCommand<String>() 
     { 
      public String execute() 
      { 
       return "result"; 
      } 
     }); 
     Long numericResult = bcp.execute(new BasicCommand<Long>() 
     { 
      public Long execute() 
      { 
       return 123L; 
      } 
     }); 
    } 
} 

Zasadniczo chcę rodzajową „proces” metoda dyktować rodzaj parametru rodzajowego obiektu Command. Celem jest umożliwienie ograniczenia różnych implementacji CommandProcessor do niektórych klas implementujących interfejs poleceń, a jednocześnie umożliwienie wywołania metody procesu dowolnej klasy, która implementuje interfejs CommandProcessor i zwrócenie obiektu typu określonego przez sparametryzowany obiekt polecenia. Nie jestem pewien, czy moje wyjaśnienie jest wystarczająco jasne, więc proszę dać mi znać, czy potrzebne są dalsze wyjaśnienia. Chyba pytanie brzmi: "Czy to w ogóle możliwe?" Jeśli odpowiedź brzmi "Nie", to co byłoby najlepszym obejściem (pomyślałem o sobie samemu, ale chciałbym trochę świeżych pomysłów).

+0

Czy 'BasicCommand' nie powinien implementować' Command'? –

+0

Touche, naprawione. Dziękujemy za złapanie tego! – Andrey

Odpowiedz

3

Niestety, nie możesz tego zrobić. Ponieważ chcesz, aby interfejs CommandProcessor był zdefiniowany pod kątem Command, twoja implementacja musi być przygotowana na instancję dowolnego typu Command - generics nie może ograniczyć tego do BasicCommand - jeśli mogłoby, wtedy podklasa BasicCommandProcessor nie implementowałaby interfejsu CommandProcessor.

Albo, pod innym kątem, z interfejsem CommandProcessor, generics nie ma możliwości upewnienia się, że zostało to wywołane tylko z instancjami BasicCommand. Aby to zrobić, trzeba znać implementację i byłoby to sprzeczne z punktem polimorfizmu i interfejsów.

Można sparametryzować wynik polecenia, ale nie konkretną klasę poleceń.

Najprostszym sposobem jest zapewnienie metody, która akceptuje konkretny typ, którego potrzebujesz, i wywołanie go w metodzie ogólnej. (Patrz BasicCommandProcessor powyżej.)

+0

"... biorąc pod uwagę interfejs CommandProcessor, generics nie jest w stanie zapewnić, że zostało to wywołane tylko z instancjami BasicCommand. Aby to zrobić, trzeba znać implementację i byłoby to sprzeczne z punktem polimorfizmu i interfejsów." Oczywiście, że jest. Jeśli implementacja jest określona jako parametr typu, podklasa może dodatkowo ograniczyć związany typ, zobacz moją odpowiedź dla przykładu. Tak, oznacza to, że typy surowe nie są zgodne z zasadą podstawiania, a kompilator musi emitować metodę syntetyczną, aby rzucić argument metody. Ale jest to poprawna Java. – meriton

+0

Widzę, co mówisz. Można go uruchomić, dodając dodatkowe typy parametrów do szczegółów implementacji, ale to mi pomniejsza sens interfejsu. Z mojego doświadczenia wynika, że ​​praca z wieloma komputerami staje się nieporęczna, ponieważ rośnie liczba interfejsów. – mdma

+0

To zależy od intencji. Jeśli CommandProcessor może pracować z wszystkimi rodzajami poleceń, które mają typ polecenia w interfejsie, rzeczywiście jest bezużyteczny. Jednakże, jeśli CommandProcessor może działać tylko z niektórymi rodzajami poleceń (jak to się tutaj wydaje), wyrażając, że w interfejsie może być odpowiednie. (Zobacz "Czy generics pomagają w projektowaniu równoległych hierarchii klas?" Pod adresem http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html) – meriton

1

Zasadniczo chcę rodzajową „proces” metoda dyktować rodzaj generycznego parametru Command obiektu.

Jest to w sprzeczności z pojęciem definiowania polecenia jako typ parametru do rodzaju otaczającej: Przy uruchamianiu tego CommandProcessor, rzeczywisty typ takich jak Command<String> mógłby być dostarczany do C. Byłoby to nawet możliwe, aby dostarczyć typu non-generic takiego jak

class Foo implements Command<String> { 
    ... 
} 

Jaki sens C<R> byłoby wtedy? Foo<R>? Command<String><R>? Command<R>?

Jakie masz opcje? Jeśli tylko CommandProcessor trzeba pracować z konkretnym rodzajem powrotu, można zrobić:

class CommandProcessor<R, C extends Command<R>> { 
    R process(C command); 
} 

class FancyCommandProcessor<R, C extends FancyCommand<R>> extends CommandProcessor<R,C> { 

} 

Jednak podejrzewam chcesz CommandProcessor pracować z całą rodziną typu poleceń.To samo w sobie byłoby żadnego problemu, po prostu oświadczyć:

<R> R process(FancyCommand<R> command); 

Jeśli jednak chcemy dodatkowo relację między CommandProcessors podtypu dla różnych rodzin poleceń, dzięki czemu można zastąpić process, venture poza ekspresyjności rodzajowych Java. W szczególności potrzebowałbyś albo odpowiednika parametrów typu "szablonu" w C++ (które pozwalają przekazać szablon jako rzeczywisty typ argumentu), albo możliwości przechwytywania parametru typu Command, biorąc pod uwagę faktyczny typ argumentu, o którym wiadomo, że rozszerza komendę . Java nie obsługuje ani.

+0

"To znaczy, że podczas tworzenia instancji CommandProcessor rzeczywisty typ, taki jak Command , musi być dostarczane dla C " Właściwie się mylisz. A [dzika karta] (http://java.sun.com/docs/books/tutorial/extra/generics/wildcards.html) (komenda ) może być zawsze dostarczana ... – Andrey

+0

Poprawiam się i odpowiednio edytuję. Nie zmienia to jednak zbytnio mojej argumentacji ... – meriton

+0

Dobrze opisałeś ten problem. Jedyną rzeczą jest to, że CommandProcessor faktycznie nie musi wiedzieć o typie zwracanego polecenia, z którym może sobie poradzić, z wyjątkiem sytuacji, gdy chodzi o przetwarzanie ich, w którym to przypadku wystarczy przekazać obiekt typu, z którym polecenie został sparametryzowany, co nie jest możliwe, aby to zrobić. Awansuj z powrotem. – Andrey

Powiązane problemy