2010-09-16 5 views
2

J'ai une classe de commande comme suit:Comment utiliser Class.cast() correctement?

public class Command { 
    ... 
    private String commandName; 
    private Object[] commandArgs; 
    ... 
    public void executeCommand() {} 
} 

J'ai aussi une sous-classe de commandement, AuthenticateCommand:

public class AuthenticateCommand extends Command { 
    ... 
    @Override 
    public void executeCommand() { 
     ... 
    } 
} 

Maintenant, imaginez une classe, serveur, qui a une méthode processCommand (commande de commande). Il prend la commande param, inspecte le champ commandName et utilise ce nom pour transtyper la commande dans une sous-classe de la commande responsable de l'implémentation de la logique de commande. Dans cet exemple, vous pouvez avoir une commande avec un nom de commande "authenticate" et le nom d'utilisateur et pw stockés dans le tableau commandArgs. processCommand() transtype la commande en AutheticateCommand et appelle la méthode executeCommand(). Je suis en train d'y arriver avec ce qui suit (commandMap est juste une carte qui associe un commandName à son nom de classe implementor):

public void processCommand(Command command) { 
    String commandName = command.getCommandName(); 
    String implementorClassString = commandMap.get(commandName); 
    try { 
     Class implementorClass = Class.forName(implementorClassString); 
     Object implementor = implementorClass.cast(command); 
     Method method = implementorClass.getDeclaredMethod("executeCommand", null); 
     method.invoke(implementor); 
    } catch (ClassNotFoundException e) { 
     logger.error("Could not find implementor class: " + implementorClassString, e); 
    } catch (NoSuchMethodException e) { 
     logger.error("Could not find executeCommand method on implementor class: " + implementorClassString, e); 
    } catch (IllegalAccessException e) { 
     logger.error("Could not access private member/method on implementor class: " + implementorClassString, e); 
    } catch (InvocationTargetException e) { 
     logger.error("Could not invoke executeCommand method on implementor class: " + implementorClassString, e); 
    } 
} 

L'appel à implementorClass.cast() jette un ClassCastException. Ne devrait-il pas pouvoir descendre à la classe AuthenticateCommand de cette manière?

MISE À JOUR

Un peu plus fond. La classe Server gère plus que simplement AuthenticateCommands. Il pourrait y avoir n'importe quel nombre de sous-classes de commande, selon le projet. J'essaye de le rendre simple pour quelqu'un écrivant un client pour passer un objet de commande sérialisé avec juste un nom et des arguments. Je pourrais forcer le client à "connaitre" AuthenticateCommand et tous les autres, puis sérialiser ceux-ci et les passer, mais cela semble sous-optimal car la seule différence entre les sous-classes est l'implémentation de executeCommand, dont le client ne se soucie pas ou savoir. Donc, je veux juste un moyen de faire passer le client à la classe parente, et utiliser les données dans cette classe parente pour le convertir en sous-classe appropriée. Je suppose que je pourrais utiliser newInstance() au lieu de lancer et juste créer un nouvel objet, mais cela semble inutile. Je suppose que je pourrais également faire disparaître le concept de sous-classes manipulant la logique et les déplacer dans des méthodes, et ensuite processCommand appelle la méthode appropriée. Cela me semble aussi bien.

Répondre

5

Pourquoi lancez-vous du tout? Vous êtes juste essayer d'appeler executeCommand, et qui est disponible sur commande ... donc il suffit d'écrire:

command.executeCommand(); 

qui devrait compiler et exécuter. Ce n'est pas clair où la carte entre.

Quant à savoir pourquoi le casting échoue ... ma conjecture est que le ClassLoader pour la commande est le ClassLoader par défaut à ce stade, de sorte que implementorClass est la même classe, mais chargé par un autre ClassLoader. .. ce qui en fait une classe de différence en ce qui concerne la JVM.

EDIT: Je dirais que votre design est cassé. L'objet Command que vous êtes passé ne remplit pas correctement son rôle. Une option serait d'avoir une nouvelle sous-classe RemoteCommand qui connaît le nom, et quand sa méthode executeCommand est appelée, elle construit l'instance de sous-classe appropriée. Et oui, aura besoin de construire une instance de la classe. Vous ne pouvez pas appeler une méthode d'instance sur une classe sans une instance de cette classe, et vous ne pouvez pas faire en sorte qu'un objet "prétende" qu'il s'agit en fait d'un objet d'un type différent. Que se passe-t-il si AuthenticationCommand a des champs supplémentaires qu'il essaie d'utiliser? D'où viendraient les valeurs?

Une plus belle alternative est de rendre votre couche de sérialisation/désérialisation faire cela, de sorte que le temps que vous avez atteint ce morceau de code, vous avez déjà obtenu un AuthenticationCommand - et vous pouvez utiliser le code à la en haut de cette réponse.

+1

Je pense que vous n'avez pas remarqué. Il essaie de lancer Command to AuthenticateCommand en fonction de commandName passé. Ainsi, Method reçoit réellement l'objet Command et non AuthenticateCommand. – YoK

+2

Jon a raison, c'est juste un polymorphisme, ou plutôt un manque de compréhension de son fonctionnement. –

+1

@YoK - la distribution (comme présentée ici) n'est pas nécessaire, Richard lance l'instance de commande sur le type de classe implenting mais stocke le résultat dans une variable 'Object' ... –

1

Vous ne pouvez pas effectuer un downcast uniquement lorsque l'objet de commande référence réellement l'instance de commande d'authentification au moment de l'exécution. C'est ce que le polymorphisme dit n'est-ce pas?

3

Vous avez vraiment besoin de l'instancier. Vous ne pouvez pas "convertir" un Class<T> en une instance concrète en le castant. En outre, la conversion doit être effectuée dans l'autre sens, contrairement à votre extrait de code.

Class<?> implementorClass = Class.forName(implementorClassString); 
Command instance = Command.class.cast(implementorClass.newInstance()); 
instance.executeCommand(); 

Sans oublier que tout cela est une odeur de design.

+0

pourquoi cette réponse n'est pas upvoted? +1 –

Questions connexes