1

我愿意为这种常见场景选择设计模式:

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

My_Message1,My_Message2 扩展了 My_Message_Abstract。

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

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

我想摆脱这个锅炉 if/then 代码并拥有更好的未来维护/动态/插件能力/整洁的方式。

所以我采用了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)。

作为该线程提供的解决方案: Java 中的 if 语句长列表

知道如何改进吗?Mybe 我应该为这个想法使用其他模式吗?

谢谢,

4

6 回答 6

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
}    
于 2013-03-27T13:31:49.587 回答
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为所有消息触发一个 which。

很容易扩展它以使用Something适用于的类数组。

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

我的观点是——通过这种机制——你可以实现几乎任何你喜欢的策略,而不必积极地修改代码。

于 2013-03-27T14:02:15.167 回答
0

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

现在,您的消息侦听器要求工厂根据类型给出适当的消息命令,然后每个消息命令都包含有效处理命令 (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();
      }
   }
于 2013-03-27T13:34:40.197 回答
0

所以实际上你MessageListener做了两个步骤:

  1. 弄清楚收到了什么消息
  2. 运行适当的命令

您可以像@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每个消息类型都有一个列表),甚至可能已经存在于您的上下文中。

于 2013-03-27T13:43:11.043 回答
0

在类似的情况下,我从您已有的开始,但添加了一个方法:

addMessageListener(int messageType, MessageCommand messageCommand)

此外,其他类需要一种方法来“定位”您的类。最简单的方法是将上述方法设为公共静态,如果这适用于您的环境……但也可以酌情使用其他发现方式。

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

如果两个命令希望收听相同的消息,则很容易通过将地图的“值”侧变为列表来进行调整

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

示例 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)
  {

  }
}
于 2013-03-27T13:43:56.107 回答
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");

    }
}
于 2013-03-27T14:32:08.770 回答