Java 摆脱开关箱块

Java 摆脱开关箱块,java,if-statement,design-patterns,switch-statement,object-oriented-analysis,Java,If Statement,Design Patterns,Switch Statement,Object Oriented Analysis,如何将此开关盒块转换为良好的代码 private static void agentFieldContructor(Agent agent, String nodeName, String value) { switch (nodeName) { case "Description": agent.setDescription(value); break; case "Model":

如何将此开关盒块转换为良好的代码

private static void agentFieldContructor(Agent agent, String nodeName, String value) {

    switch (nodeName) {

        case "Description":
            agent.setDescription(value);
            break;

        case "Model":
            agent.setModel(value);
            break;

        (... +18)

    }

}
agent
是一个对象,我根据指定的
nodeName
参数填充它。

每个节点名引用一个不同的代理属性,但我像字符串一样接收它,我无法更改它。我搜索了一些设计模式,但找不到任何可以帮助我的东西。

这里的问题不在于switch case语句本身。而是代理类本身的设计。首先,代理应该有一个合适的构造函数,您可以在其中传入属性值。如果这些值可以更改,那么代理的用户应该直接调用setters,而不是使用这个helper函数


更重要的是,具有20个属性的类很可能是重构的候选对象。尝试查找这些属性子集之间的关系,这些属性可以分解为单独的类。

如果允许使用反射

private static void agentFieldContructor(Agent agent, String nodeName, String value) {
    Method setMethod =  Agent.class.getMethod("set"+nodeName,String.class)
    setMethod.invoke(Agent,value)
}

我完全同意这些评论。但是,如果您确实不想使用switch case,并且您的方法名称和case字符串几乎相同,那么您可以使用反射。是可以帮助您的解决方案。

实现这一点的一个简单方法是通过反射。您可以访问setter方法并使用您的值调用它

public static void agentFieldConstructor(Agent agent, String nodeName, String value) {
    try {
        agent.getClass().getDeclaredMethod("set" + nodeName, value.getClass()).invoke(agent, value);
    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        e.printStackTrace();
    }
}
请注意,您必须处理:

  • 如果他们插入了无效的节点名,会发生什么
  • 如果值是意外值(如
    null
    ),会发生什么情况
  • 处理大小写混合的节点名称(现在,如果第一个字母大写,则此操作有效)
  • 处理非标准节点名称(这将在很大程度上假定JavaBean命名约定)

…所有这些都是读者的练习。

不使用开关大小写或反射的另一种可能性是使用复杂枚举。这里的枚举是从字符串键到setter调用的转换器

public enum AgentFields {
    DESCRIPTION {
        @Override
        public void setInAgent(Agent agent, String value) {
            agent.setDescription(value);
        }
    },

    MODEL {
        @Override
        public void setInAgent(Agent agent, String value) {
            agent.setModel(value);
        }
    };

    public abstract void setInAgent(Agent agent, String value);

    // Call this method to set a named field's value
    public static void agentFieldSetter(Agent agent, String nodeName, String value) {
        AgentFields.valueOf(nodeName.toUpperCase()).setInAgent(agent, value);
    }

}


这声明了一个枚举,每个字段有一个“值”,每个值都带有一个单独的setter。调度是使用所有枚举自动提供的valueOf()方法完成的。我用toUpperCase修饰它,以允许通常的大写枚举值约定,并使其不区分大小写。

是什么让您认为开关…大小写不是好代码?你必须使用你所拥有的,如果你以字符串的形式接收它,一个switch…case和任何方法一样有效。你的switch不一定是坏的。。。。然而,看看小海狸。我在面向对象语言中学到的是:如果你有一个巨大的If-else块,或者switch-case,那么你是做错了什么。只是要明确一点:你试图通过
value
?switch-case本身并不一定是坏代码。在这种情况下,您应该在
代理
类中编写一个合适的构造函数,或者要求直接调用setter而不是通过此方法。如果我可以一次获取每个值,那么构造函数可能是一个不错的选择,但现在我一次只获取一个值。代理类用于保存在数据库中,所以我不能重构这一个。“但现在我一次只能得到一个”,然后直接调用setters。我看不出你在原始问题中发布的方法有什么好的理由。当然,我的回答和评论仅基于您在此处发布的内容。我不知道
nodeName
从何而来,因此使用前面建议的反射可能是更好的解决方案。是的,我正在调用setter,我的逻辑是:如果nodeNade是“Model”,那么调用setModel。但我有20个属性,所以我是在一个有20个案例的swtich中做这件事的。现在我正在使用反射,我不知道它,它很有帮助!!:)@MarcusRigonati nodeName来自哪里?是的,这是对OP问题的回答。但与switch构造相比:它速度慢得多,可读性差,它将一些错误检查从编译时转移到运行时,像Eclipse的“调用层次结构”这样的IDE工具看不到的隐式调用,例如
setModel()
。所以我建议尽量远离反射。@MarcusRigonati注意到,尽管这是可行的,但这并不是反射的预期用途。事实上,您采用这种方法意味着代理的原始设计和使用它的代码存在问题。@代码学徒:考虑到反射的预期用途是允许这样的元编程,我不确定我是否同意您的评估。我并不反对它的速度慢,而且这完全是非标准的,但这确实是一个有效的反射用例。@RalfKleberhoff:OP发现自己处于一个无法准确重新构建此对象的位置。你还推荐什么方法?@Makoto没有更多信息,我不能再推荐了。特别是,了解
nodeName
来自何处会很有帮助。我确实想过使用enum类,但我遇到了一个困难:我不知道如何使用。我试着学习,但看起来很复杂,所以我暂时放弃了。不管怎么说,我在处理字符串,比如如果我的字符串是“Model”,我将调用setModel(),所以Enum可能不会有太大帮助。也许你没有认识到agentFieldSetter()方法,它接受字符串作为字段鉴别器,所以这里Enum不是映射键,而是从字符串键到setter的转换器。现在我明白了,这真的可以帮助我,因为其他的解决方案就像一个杰里钻机(我不知道如何用英语说“冈比亚拉”)。如果有帮助的话,我会做测试并回来!!谢谢