设计混乱if / then逻辑的建议

时间:2013-03-27 13:27:49

标签: java oop design-patterns

我愿意为这种常见情况选择设计模式:

我有一个接收消息的模块(MessageListener)。 它收到的Eeach消息实际上是一个对象(MyMessage1,MyMessage2,..)

My_Message1,My_Message2扩展了My_Message_Abstract。

现在,当MessageListener对象(onMessage(..))检索到一条消息时 我想做一个不同的“命令”取决于消息实例..这样的事情:

onMessage(My_Message_Abstract msg)
{
   if (msg instance of My_Message1)
   {
      doSomething()..
   }
   else if(msg instance of My_Message2)
   {
     doSomethingElse()..
    }
}

我希望摆脱这个锅炉,如果/然后代码,并有更好的未来 - 维护/动态/插件/整洁的方式。

所以我采用了Command设计模式。我发现我可以有类似的东西:

在MessageListener中

有一张地图:

Map<Integer, MessageCommand> messageCommandsMap = new HashMap<Integer, MessageCommand>();

..

sessionTargetMap.put(MSG_1_TYPE, new Message1Command());
sessionTargetMap.put(MSG_2_TYPE, new Message2Command());

(Message1Command,Message2Command implements from Command interface)

onMessage(My_Message_Abstract msg)
{
messageCommandsMap.get(msg.getType).executeCommand(msg);
}

我不喜欢MessageListeenr中的hashmap想法,这样我将所有命令耦合到该对象(MessageListener)。

正如此主题提供的解决方案: Long list of if statements in Java

任何想法我怎么能改善这个? Mybe我应该用其他模式来实现这个想法吗?

感谢,

6 个答案:

答案 0 :(得分:3)

我采用的方法是使用通用接口来装饰这些类,这使我能够利用Java函数的虚拟特性。例如,我会这样做:

public interface CoolInterface  
{
      void doSomething();  
}  

My_Message_Abstract implements CoolInterface  
{  
      public abstract void doSomething();
}  
Message1Command extends My_Message_Abstract
{  
       public void doSomething(){ System.out.println("First");
}    
Message2Command extends My_Message_Abstract
{  
       public void doSomething(){ System.out.println("");
}   

您的代码现在变为:

onMessage(My_Message_Abstract msg)
{
  msg.doSomething();
}

如果您想委派,请执行以下操作:

 My_Message_Abstract implements CoolInterface  
    {  
          public void doSomething()
          {
             System.out.println("Default");
          }  
    }    

   Message1Command extends My_Message_Abstract
{  
       public void doSomething(){ System.out.println("First");
}    
Message2Command extends My_Message_Abstract
{  
       // no need to override the method just invoke doSomething as normal
}    

答案 1 :(得分:1)

当您想要可能的事情列表时,我总是喜欢使用enum

public class Test {
  // A type of message.
  class MyMessage1 {
  };
  // A whole set of message types.

  interface MyMessage2 {
  };

  // The Dos - To demonstrate we just print something.
  enum Do {
    Something(MyMessage1.class) {
      @Override
      void doIt() {
        System.out.println("Something");
      }
    },
    SomethngElse(MyMessage2.class) {
      @Override
      void doIt() {
        System.out.println("Something else");
      }
    },
    Everything(Object.class) {
      @Override
      void doIt() {
        System.out.println("Everything");
      }
    };
    // Which classes this one applies to - could use an array of Class here just as easily.
    final Set<Class> applies = new HashSet<Class>();
    // You can add multiples on construction.
    Do(Class... applies) {
      this.applies.addAll(Arrays.asList(applies));
    }

    // Perform all that are relevant to this message type.
    static void doIt(Class messageClass) {
      for (Do d : Do.values()) {
        // If it is assignable
        boolean doIt = false;
        for (Class c : d.applies) {
          if (c.isAssignableFrom(messageClass)) {
            doIt = true;
          }
        }
        if (doIt) {
          // Execute the function.
          d.doIt();
        }
      }
    }

    // What to do.
    abstract void doIt();
  }

  public void test() {
    System.out.println("Hello");
    // Test with a concrete message.
    onMessage(new MyMessage1());
    // And an implementation of an interface.
    onMessage(new MyMessage2() {
    });
  }

  private void onMessage(Object message) {
    // Do something depending on the class of the message.
    Do.doIt(message.getClass());
  }

  public static void main(String args[]) {
    new Test().test();
  }
}

使用此模式,您甚至可以为所有消息触发Everything

扩展它很容易使用这个Something适用的类数组。

如果您只想要应用第一个匹配项,请在适当的位置突破循环。

我的观点是 - 使用这种机制 - 你可以实现几乎任何你喜欢的策略,而不必积极地破解代码。

答案 2 :(得分:0)

你可以拥有一个MessageCommand Factory,它基本上知道哪个MessageCommand用于给定的消息类型。在工厂中,您可以使用map或if / else来识别命令类。

现在,您的消息侦听器要求工厂根据类型提供相应的Message Command,然后每个Message Command都保存逻辑以有效地处理命令(doSomethingElse)方法。

代码如何显示的抽象概念:

   class MessageCommandFactory {
       Command get(MessageType messageType) {
         if(messageType == MSG_1_TYPE) {
           return new Message1Command();
         } else ...
       } 

    class MessageListener {
    MessageCommandFactory messageCommandFactor;
        onMessage(My_Absctract_Message message) {
          Command command =  messageCommandFactory.get(message.getType());
          command.execute();
      }
   }

答案 3 :(得分:0)

实际上,您的MessageListener执行了两个步骤:

  1. 找出收到的消息
  2. 运行适当的命令
  3. 您可以将“running”委托给@Woot4Moo的消息本身,或者将MessageListener子类委托为消息执行特定的操作,如下所示:

    public class Message1Listener implements MessageListener {
      onMessage(SpecificMessage1 msg) {
        /* do something with msg */
      }
    }
    
    public class MessageBus {
      Map<Class<? extends Message>, MessageListener> listeners = new HashMap<>();
      void register(MessageListener listener, Class<? extends Message> msgType) {
        listeners.put(msgType, listener);
      }
    
      void onMessage(Message msg) {
        listeners.get(msg.getClass()).onMessage(msg);
      }
    }
    

    您的MessageListener已成为MessageBus,代理已注册的处理程序。 这是一个非常基本设置,您可以根据需要进行扩展(即每个消息类型都有MessageListener列表),甚至可能已存在于您的上下文中。

答案 4 :(得分:0)

在类似的情况下,我已经开始使用你已有的东西,但添加了一个方法:

addMessageListener(int messageType, MessageCommand messageCommand)

此外,其他课程需要一种“定位”你的课程的方法。最简单的方法是将上述方法用于公共静态,如果它适用于您的环境......但是可以酌情使用其他发现方式。

该方法只是添加到您已有的地图中。

如果两个命令希望收听同一条消息,那么通过将地图的“值”一边变成一个List

就可以轻松调整它

此外,您始终可以使用已经“预先注册”的某些预先确定的命令启动地图,或者从某种类型的配置中读取预注册信息......等等。

示例1:假设您有一个名为NewMessageHandler的新类。该类需要处理全新的消息类型。在某些适当的时候,当NewMessageHandler初始化自己时,if也可以将自己注册为监听器。它必须“找到”调度程序类(希望这是一个单例),然后它可以调用addMessageListener()。

示例2: Dispatcher类可以读取配置文件,该文件定义将处理消息的消息类型和类对。

public class Dispatcher 
{
  public static Dispatcher getInstance()
  {
     //return the instance. Could accept parms for more complex needs
  }

  public void addMessageListener(int messageType, MessageCommand messageCommand)
  {
    //Add to the internal map
  }

  private void init()
  {
     //Optionally, read config file or System properties, and call addMessageListener()
  }

  private void dispatchMessage(Message msg)
  {
    //Look up map and dispatch to the registered instance
    //Call the handleMessage() method on the appropriate listener      
  }

}

界面

public interface MessageCommand 
{
   public void handleMessage(Message msg);
}

和另一个......

public class NewMessageHandler implements MessageCommand 
{

   private void init()
   {
      Dispatcher.addMessageListener(666, this)
   }

  public void handleMessage(Message msg)
  {

  }
}

答案 5 :(得分:0)

这里的基本问题是消息是数据,您需要根据该数据的属性进行调度。因此,您需要更改数据对象以分派给不同的处理程序,或者您需要在侦听器中使用调度表。无论哪种方式,您都需要代码来处理调度。

IMO,消息是实现调度的错误的位置。它是数据,行为是听众的责任。如果你有两个听众,你会想要使用不同的发送表。

对于我自己的实现,我可能会使用if-else链来进行多达五到六次可能的干扰。在上面,一个算子地图。这些更容易,而且很明显发生了什么。

但是,如果你真的不喜欢简单的方法,那么这里就是使用反射的方法:

public class Dispatcher {

    public static class Foo {}
    public static class Bar {}


    public void dispatch(Object obj) throws Exception {
        try {
            Method handler = this.getClass().getDeclaredMethod("handler", obj.getClass());
            handler.invoke(this, obj);
        }
        catch (Exception e) {
            System.out.println("couldn't determine handler for " + obj.getClass().getName());
        }
    }

    private void handler(Foo foo) {
        System.out.println("handler(Foo)");
    }

    private void handler(Bar bar) {
        System.out.println("handler(Bar)");
    }


    public static void main(String[] argv) throws Exception {

        Dispatcher dispatcher = new Dispatcher();

        dispatcher.dispatch(new Foo());
        dispatcher.dispatch(new Bar());
        dispatcher.dispatch("something else");

    }
}