【问题标题】:tagging methods and calling them from a client object by tag标记方法并通过标记从客户端对象调用它们
【发布时间】:2010-11-26 21:12:43
【问题描述】:

我一直在尝试找出一种方法来标记我的基类中的几个方法,以便客户端类可以通过标记调用它们。示例代码为:

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

来自 main() 方法的客户端类将通过随机指令序列调用 Base 的每个方法:

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

    }

现在我希望从 Client 对象中完全摆脱 switch 语句。我知道用多态性替换 switch 的技术,但希望 避免 创建一组新类。我希望将这些方法简单地存储在适当的数据结构中,并以某种方式用序列中的匹配字符标记它们。

映射可以轻松地存储具有可以完成工作的值/键对的对象(就像我所做的here)或命令模式,但由于我不想用对象替换这些方法,是否存在也许是另一种方式,存储方法并让客户端有选择地调用它们?

感谢任何建议

【问题讨论】:

    标签: java design-patterns oop methods switch-statement


    【解决方案1】:

    这样的东西?

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

    然后

        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:感谢您的详细说明,它看起来非常简洁明了
    • +1 是最简单的解决方案,尽管我仍然认为放弃反射开销并使用 命令模式 会更好。
    • 可能,但我怀疑它。如今,反射开销非常小,而且对于几乎所有 Java 框架来说肯定足够快。
    【解决方案2】:

    我会在有问题的方法上使用annotations,允许将其标记为“标记方法”,并提供用于该方法的标记字符串。

    从那时起,实现变得更简单了;您可以使用反射来迭代类的方法并检查它们的注释;也许在启动时静态执行此操作并填充从标记字符串到 java.lang.reflect.Method 的映射。

    然后在处理命令字符串时,调用每个标签对应的方法。

    编辑:一些示例代码:

    import java.lang.annotation.*; 
    
    @Retention(RetentionPolicy.RUNTIME)
    @interface TaggedMethod {
        String tag();
    }
    

    然后在基类中:

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

    ...在客户端:

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

    这样您就可以通过以下方式访问它:

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

    顺便说一句,此代码尚未编译或测试... :-)

    【讨论】:

    • @dtsazza:我对注释不太熟悉,所以感谢您让我注意到这一点。我很乐意尝试它只是为了了解有关注释的更多信息
    • @denchr - 不客气。查看 skaffman 的答案,这本质上是相似的(创建一个到 Method 对象的标签映射,然后进行查找并调用它们)。不同之处在于,这里的映射被声明为方法本身的属性,而不是显式映射。 skaffman 的方法可能更适合方法不多的小型项目(因为它更易于阅读),我的可能扩展性更好。
    • 这个解决方案的问题是你不能将方法与它们的标签解耦,这取决于具体情况,要么是优势,要么是劣势。
    • @skaffman:是的;我知道Base 类的唯一目的是处理这些映射。如果它是一个具有您想要映射到的另一个目的的类,那么可以说映射将“属于” Main 类,并且您的方法会更合适。
    【解决方案3】:

    您可以在 C# 中为此使用属性。对于 Java,使用注解。从 Attribute 类派生一个类,例如 TagAttribute,并将该属性应用于方法。

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

    将属性应用于方法:

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

    使用反射调用方法:

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

    从您的 Main 方法调用 CallTaggedMethod:

    static void Main(string[] args)
    {
        String sequence = "ABCAABBBABACCACC";
        MyClass inst = new MyClass();
    
        foreach(char c in sequence)
            CallTaggedMethod(inst, c);
    
        // The rest.
    
        Console.ReadLine();
    }
    

    【讨论】:

      【解决方案4】:

      这是我的注释方法。如果您使用注释,您甚至不需要标签到方法的 Map,只需遍历序列并使用反射查找该标签的方法。

      import java.lang.annotation.*;
      
      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface Tag {
         char value();
      }
      

      然后:

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

      然后

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

      【讨论】:

      • 其实我应该修改 getMethodForCharacter 以使用某种形式的缓存
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-01
      • 2016-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多