views:

104

answers:

4

Hello,

I have been trying to figure out a way to tag several methods from my base class, so that a client class can call them by tag. The example code is:

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

A client class from a main() method will call each method of Base through a random instruction sequence:

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

    }

Now I wish to get rid of the switch statement altogether from the Client object. I am aware of the technique to replace switch by polymorphism, but would like to avoid creating a set of new classes. I was hoping to simply store those methods in an appropriate data structure and somehow tag them with a matching character from the sequence.

A map could easily store objects with value/key pairs which could do the job, (as I did here), or the command pattern, but since I don't want to replace those methods with objects, is there a different way perhaps, to store methods and have a client selectively call them?

Any advice is appreciated

+5  A: 

I would use annotations on the methods in question, allowing it to be marked as a "tagged method", and providing the tag string to use for that method.

From that point the implementation gets simpler; you can use reflection to iterate over a class' methods and inspect their annotations; perhaps do this statically at startup and populate a mapping from tag string to java.lang.reflect.Method.

Then when processing the command string, invoke the methods that correspond to each tag.

Edit: some example code:

import java.lang.annotation.*; 

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

Then in the base class:

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

...and in the 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);
      }
   }
}

so that you can access this as:

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

}

This code hasn't been compiled or tested, by the way... :-)

Andrzej Doyle
@dtsazza: I wasn't too familiar with annotations so thank you for bringing this to my attention. I would gladly try it just to learn more about annotations
denchr
@denchr - you're welcome. Looking at skaffman's answer, this is essentially similar (creating a map of tag to Method objects, then doing the lookup and invoking them). The difference is that here the mappings are declared as properties of the methods themselves, rather than in an explicit mapping. skaffman's approach is probably better for smaller projects with not many methods (as it's more straightforward to read), mine probably scales a lot better.
Andrzej Doyle
The problem with this solution is that you can't decouple the methods from their tags, which is either a strength or a weakness depending on the situation.
skaffman
@skaffman: true; I understood that the sole purpose of the `Base` class was to handle these mappings. If it was a class that had another purpose which you wanted to map to, then arguably the mappings would "belong" to the Main class and your method would be more appropriate.
Andrzej Doyle
+6  A: 

Something like this?

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

}

and then

    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);
     }
    }
skaffman
@skaffman: Thanks for elaborating, its looks pretty neat and straighforward
denchr
+1 for the simplest solution, although I still think ditching the reflection overhead and using the *command pattern* would be preferable.
Nick Holt
Possibly, but I doubt it. Reflection overhead is pretty small these days, and is certainly fast enough for pretty much every java framework out there.
skaffman
+1  A: 

You could use Attributes for this, in C#. For Java, use annotations. Derive a class from the Attribute class, say, TagAttribute, and apply the attribute to the methods.

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

Apply the attribute to the methods:

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

Invoke the methods using reflection:

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

Call the CallTaggedMethod from your Main method:

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

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

    // The rest.

    Console.ReadLine();
}
Virtlink
A: 

Here is my annotations Approach. You don't even need a Map of tags to methods if you are using annotations, just iterate over the sequence and lookup the method for that tag using reflection.

import java.lang.annotation.*;

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

then:

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

then

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;
  }
}
Tarski
Actually, I should probably modify getMethodForCharacter to use some form of caching
Tarski