2010-06-12 8 views
6

Mi pregunta no es fácil de explicar con palabras, afortunadamente no es muy difícil de demostrar. Por lo tanto, tengan paciencia conmigo:¿Cómo mezclar adecuadamente los genéricos y la herencia para obtener el resultado deseado?

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

Básicamente, quiero que el método de "proceso" genérico para dictar el tipo de parámetro genérico del objeto Command. El objetivo es poder restringir diferentes implementaciones de CommandProcessor a ciertas clases que implementan la interfaz Command y al mismo tiempo poder invocar el método de proceso de cualquier clase que implemente la interfaz CommandProcessor y hacer que devuelva el objeto de tipo especificado por el Objeto de comando parametarizado. No estoy seguro si mi explicación es lo suficientemente clara, así que por favor avíseme si se necesita una explicación más detallada. Supongo que la pregunta es "¿Sería posible hacer esto?" Si la respuesta es "No", cuál sería la mejor solución (pensé en una pareja sola, pero me gustaría algunas ideas nuevas)

+0

¿No debería 'BasicCommand' implementar' Command'? –

+0

Touche, arreglado. Gracias por atrapar eso! – Andrey

Respuesta

3

Lamentablemente, no puede hacer esto. Dado que desea que la interfaz CommandProcessor se defina en términos de Command, su implementación debe prepararse para tomar cualquier tipo de instancia Command - los genéricos no pueden restringir esto a BasicCommand - si pudiera, entonces la subclase BasicCommandProcessor no implementaría la interfaz CommandProcessor.

O, desde otro ángulo, dada una interfaz CommandProcessor, no es posible que los genéricos se aseguren de que esto solo se invocó con instancias BasicCommand. Para hacer esto, necesitaría conocer la implementación, e iría en contra del punto de polimorfismo e interfaces.

Puede parametrizar el resultado del comando, pero no la clase de comando concreta.

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 
{ 
    public <R> R process(Command<R> command); 
} 

public class BasicCommandProcessor implements CommandProcessor 
{ 
    public <R> R processBasicCommand(BasicCommand<R> command) 
    { 
     return command.execute(); 
    } 

    public <R> R process(Command<R> command) 
    { 
     return processBasicCommand((BasicCommand<R>)command); 
    } 
} 

El enfoque más simple es proporcionar un método que acepte el tipo específico que necesita y llamarlo en el método genérico. (Ver BasicCommandProcessor arriba.)

+0

"... dada una interfaz CommandProcessor, no es posible que los genéricos se aseguren de que esto solo se haya invocado con instancias de BasicCommand. Para ello, necesitaríamos conocer la implementación e iríamos contra el punto de polimorfismo e interfaces". Por supuesto que es. Si la implementación se especifica como parámetro de tipo, una subclase puede restringir aún más el tipo vinculado, ver mi respuesta para un ejemplo. Sí, esto significa que los tipos crudos no se ajustan al principio de sustitución, y se requiere que el compilador emita un método sintético para convertir el argumento del método. Pero es válido Java. – meriton

+0

Veo lo que dices. Se puede hacer que funcione agregando tipos de parámetros adicionales para los detalles de la implementación, pero a mí eso le resta valor al punto de tener una interfaz. En mi experiencia, también puede ser difícil de manejar, ya que la cantidad de interfaces crece. – mdma

+0

Depende de la intención. Si el CommandProcessor puede trabajar con todo tipo de comandos que tengan el tipo de comando en la interfaz, es inútil. Sin embargo, si un CommandProcessor solo puede funcionar con ciertos tipos de comandos (como parece ser el caso aquí), expresarlo en la interfaz puede ser apropiado. (Consulte "¿Los genéricos ayudan a diseñar jerarquías de clases paralelas?" En http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html) – meriton

1

Básicamente, quiero que el método genérico "proceso" para dictar el tipo de parámetro genérico de la objeto Command.

Esto está en contradicción con la noción de que define el comando como parámetro de tipo al tipo envolvente: Cuando instanciar un CommandProcessor, un tipo real tales como Command<String> podría ser suministrada para C. Incluso sería posible suministrar un tipo no genérico como

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

¿Cuál sería el significado de C<R> ser, entonces? Foo<R>? Command<String><R>? Command<R>?

¿Qué opciones tienes? Si un CommandProcessor sólo tendrá que trabajar con un tipo específico de devolución, que puede hacer:

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

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

} 

Sin embargo, sospecho desea una CommandProcessor trabajar con toda la familia tipo de comandos.Esto en sí mismo no sería un problema, sólo tiene que declarar:

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

Sin embargo, si usted quiere, además, una relación entre el subtipo CommandProcessors para diferentes familias de comandos para que pueda anular process, se aventura más allá de la expresividad de los genéricos de Java. Específicamente, necesitaría el equivalente de los parámetros de tipo 'plantilla' de C++ (que permiten pasar una plantilla como argumento de tipo real), o la capacidad de capturar el parámetro de tipo de comando dado un argumento de tipo real que se sabe que extiende el comando . Java no admite ninguno.

+0

"Es decir, al crear instancias de CommandProcessor, debe escribirse un tipo real como Command . suministrado para C " En realidad, está equivocado. Siempre se puede suministrar un [comodín] (http://java.sun.com/docs/books/tutorial/extra/generics/wildcards.html) (Comando ) ... – Andrey

+0

Estoy corregido y he editado en consecuencia. Sin embargo, cambia poco en mi argumento ... – meriton

+0

Has descrito bien el problema. Lo único es que CommandProcessor en realidad no necesita saber sobre el tipo de devolución del comando con el que podría tratar EXCEPTO para cuando se trata de procesarlos, en cuyo caso solo debe pasar el objeto del tipo con el que el comando fue parametrizado que no es posible hacer parece. Upvote tu copia de seguridad. – Andrey

Cuestiones relacionadas