2009-09-09 7 views
5

Ho cercato di capire un modo per etichettare diversi metodi dalla mia classe base, in modo che una classe client possa chiamarli per tag. Il codice di esempio è:metodi di codifica e chiamarli da un oggetto client per tag

public class Base { 
     public void method1(){  
     ..change state of base class 
    } 

    public void method2(){  
     ..change state of base class 
    } 

    public void method3(){  
     ..change state of base class 
    } 
} 

Una classe client da un metodo main() chiamerà ogni metodo di base attraverso una sequenza di istruzioni casuale:

public static void main(String[] args) { 
String sequence = "ABCAABBBABACCACC" 
Base aBase = new Base(); 
for (int i = 0; i < sequence.length(); i++){ 
      char temp = sequence.charAt(i); 
      switch(temp){ 
      case 'A':{aBase.method1(); break;} 
      case 'B':{aBase.method2(); break;} 
      case 'C':{aBase.method3(); break;}   } 
     } 

     System.out.println(aBase.getState()); 

    } 

Ora desidero sbarazzarsi della istruzione switch del tutto dall'oggetto Client. Sono a conoscenza della tecnica per sostituire lo switch con il polimorfismo, ma vorrei evitare creando un set di nuove classi. Speravo di memorizzare semplicemente quei metodi in una struttura dati appropriata e in qualche modo taggarli con un carattere corrispondente dalla sequenza.

Una mappa potrebbe facilmente memorizzare oggetti con coppie valore/chiave che potrebbero svolgere il lavoro, (come ho fatto io here) o il modello di comando, ma poiché non voglio sostituire questi metodi con gli oggetti, c'è un forse in modo diverso, per memorizzare metodi e farli chiamare in modo selettivo da un cliente?

Ogni consiglio è apprezzato

risposta

6

Qualcosa di simile?

public class Base { 

    private final Map<Character, Method> methods = new HashMap<Character, Method>(); 

    public Base() throws SecurityException, NoSuchMethodException { 
     methods.put('A', getClass().getMethod("method1")); 
     methods.put('B', getClass().getMethod("method2")); 
     methods.put('C', getClass().getMethod("method3")); 
    } 

    public Method getMethod(char c) { 
     return methods.get(c); 
    } 

    public void method1() {} 

    public void method2() {} 

    public void method3() {} 

} 

e poi

public static void main(String[] args) throws Exception { 
     String sequence = "ABCAABBBABACCACC"; 
     Base aBase = new Base(); 

     for (int i = 0; i < sequence.length(); i++) { 
      char temp = sequence.charAt(i); 
      aBase.getMethod(temp).invoke(aBase); 
     } 
    } 
+0

@skaffman: Grazie per l'elaborazione, il suo aspetto è abbastanza semplice e diretto – denchr

+0

+1 per la soluzione più semplice, anche se continuo a pensare di abbandonare il riflesso in testa e usare il * schema di comando * sarebbe preferibile. –

+0

Forse, ma ne dubito. L'overhead di riflessione è piuttosto ridotto al giorno d'oggi, ed è certamente abbastanza veloce per quasi tutti i framework java disponibili. – skaffman

5

userei annotations sui metodi in questione, permettendo così di essere contrassegnato come un "metodo taggati", e fornendo la stringa tag da utilizzare per quel metodo.

Da quel punto l'implementazione diventa più semplice; puoi usare la riflessione per scorrere i metodi di una classe e ispezionarne le annotazioni; forse farlo staticamente all'avvio e compilare una mappatura dalla stringa di tag a java.lang.reflect.Method.

Quindi, durante l'elaborazione della stringa di comando, richiamare i metodi corrispondenti a ciascun tag.

Edit: un po 'di codice di esempio:

import java.lang.annotation.*; 

@Retention(RetentionPolicy.RUNTIME) 
@interface TaggedMethod { 
    String tag(); 
} 

Poi nella classe base:

public class Base { 

    @TaggedMethod(tag = "A") 
    public void method1(){   
    ..change state of base class 
    } 

    @TaggedMethod(tag = "B") 
    public void method2(){    
    ..change state of base class 
    } 

    @TaggedMethod(tag = "C") 
    public void method3(){    
    ..change state of base class 
    } 
} 

... e nel client:

private static final Map<String, Method> taggedMethods = new HashMap<String, Method>(); 

// Set up the tag mapping 
static 
{ 
    for (Method m : Base.class.getDeclaredMethods()) 
    { 
     TaggedMethod annotation = m.getAnnotation(TaggedMethod.class) 
     if (annotation != null) 
     { 
     taggedMethods.put(annotation.tag(), m); 
     } 
    } 
} 

in modo che sia possibile accedere questo come:

public static void main(String[] args) throws Exception 
{ 
    String sequence = "ABCAABBBABACCACC" 
    Base aBase = new Base(); 
    for (int i = 0; i < sequence.length(); i++) 
    { 
      String temp = sequence.substring(i,1); 
      Method method = taggedMethods.get(temp); 
      if (method != null) 
      { 
       // Error handling of invocation exceptions not included 
       method.invoke(aBase); 
      } 
      else 
      { 
       // Unrecognised tag - handle however 
      } 
    } 

    System.out.println(aBase.getState()); 

} 

Questo codice non è stato compilato o testato, tra l'altro ... :-)

+0

@dtsazza: non ero troppo familiarità con le annotazioni quindi grazie per aver portato questo alla mia attenzione. Lo proverei volentieri solo per saperne di più sulle annotazioni – denchr

+1

@denchr - prego. Guardando la risposta di skaffman, questo è essenzialmente simile (creando una mappa del tag sugli oggetti Method, quindi eseguendo la ricerca e invocandoli). La differenza è che qui le mappature sono dichiarate come proprietà dei metodi stessi, piuttosto che in una mappatura esplicita. L'approccio di skaffman è probabilmente migliore per i progetti più piccoli con pochi metodi (dato che è più semplice da leggere), la mia probabilmente scala molto meglio. –

+0

Il problema con questa soluzione è che non puoi disaccoppiare i metodi dai loro tag, che è un punto di forza o di debolezza a seconda della situazione. – skaffman

1

Si potrebbe utilizzare gli attributi per questo, in C#. Per Java, usare le annotazioni. Ricava una classe dalla classe Attribute, ad esempio TagAttribute e applica l'attributo ai metodi.

[global::System.AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)] 
public sealed class TagAttribute : Attribute 
{ 
    public TagAttribute(char value) 
    { 
     this.value = value; 
    } 

    private char value; 
    public char Value 
    { 
     get { return value; } 
    } 
} 

Applicare l'attributo ai metodi:

public class MyClass 
{ 
    [Tag('A')] 
    public void Method1() 
    { Console.Write("a"); } 

    [Tag('B')] 
    public void Method2() 
    { Console.Write("b"); } 

    [Tag('C')] 
    public void Method3() 
    { Console.Write("c"); } 
} 

richiamare i metodi utilizzando la riflessione:

private static void CallTaggedMethod(MyClass instance, char value) 
{ 
    MethodInfo methodToCall = null; 

    // From the MyClass type... 
    Type t = typeof(MyClass); 
    // ...get all methods. 
    MethodInfo[] methods = t.GetMethods(); 
    // For each method... 
    foreach (MethodInfo mi in methods) 
    { 
     // ...find all TagAttributes applied to it. 
     TagAttribute[] attributes = (TagAttribute[])mi.GetCustomAttributes(typeof(TagAttribute), true); 
     if (attributes.Length == 0) 
      // No attributes, continue. 
      continue; 
     // We assume that at most one attribute is applied to each method. 
     TagAttribute attr = attributes[0]; 
     if (attr.Value == value) 
     { 
      // The values match, so we call this method. 
      methodToCall = mi; 
      break; 
     } 
    } 

    if (methodToCall == null) 
     throw new InvalidOperationException("No method to call."); 

    object result = methodToCall.Invoke(
     // Instance object 
     instance, 
     // Arguments 
     new object[0]); 

    // 'result' now contains the return value. 
    // It is ignored here. 
} 

Chiamare la CallTaggedMethod dal metodo Main:

static void Main(string[] args) 
{ 
    String sequence = "ABCAABBBABACCACC"; 
    MyClass inst = new MyClass(); 

    foreach(char c in sequence) 
     CallTaggedMethod(inst, c); 

    // The rest. 

    Console.ReadLine(); 
} 
0

Qui sono le mie annotazioni Ap proccio. Non hai nemmeno bisogno di una mappa dei tag per i metodi se stai usando le annotazioni, semplicemente itera sulla sequenza e cerca il metodo per quel tag usando il reflection.

import java.lang.annotation.*; 

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
public @interface Tag { 
    char value(); 
} 

poi:

public class Base { 

    StringBuilder state = new StringBuilder(); 

    @Tag('A') 
    public void method1(){   
     state.append("1"); 
    } 

    @Tag('B') 
    public void method2(){    
    state.append("2"); 
    } 

    @Tag('C') 
    public void method3(){    
    state.append("3"); 
    } 

    public String getState() { 
    return state.toString(); 
    } 
} 

poi

public final class TagRunner { 

    private TagRunner() { 
     super(); 
    } 

    public static void main(String[] args) throws IllegalArgumentException, 
    IllegalAccessException, InvocationTargetException { 
     Base b = new Base(); 
     run(b, "ABCAABBBABACCACC"); 
     System.out.println(b.getState()); 
    } 

    private static <T> void run(T type, String sequence) throws 
    IllegalArgumentException, IllegalAccessException, InvocationTargetException { 
     CharacterIterator it = new StringCharacterIterator(sequence); 
     Class<?> taggedClass = type.getClass(); 

     for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) { 
     getMethodForCharacter(taggedClass, c).invoke(type);  
     } 
    } 

    private static Method getMethodForCharacter(Class<?> taggedClass, char c) { 
     for (Method m : taggedClass.getDeclaredMethods()) { 
     if (m.isAnnotationPresent(Tag.class)){ 
      char value = m.getAnnotation(Tag.class).value(); 
      if (c == value) { 
       return m; 
      } 
     }  
     } 

    //If we get here, there are no methods tagged with this character 
    return null; 
    } 
} 
+0

In realtà, dovrei probabilmente modificare getMethodForCharacter per usare qualche forma di caching – Tarski