Как избежать instanceof при проверке типа сообщения - PullRequest
9 голосов
/ 10 августа 2009

У меня следующая ситуация, когда клиентский класс выполняет другое поведение в зависимости от типа получаемого сообщения. Мне интересно, есть ли лучший способ сделать это, так как мне не нравятся instanceof и операторы if.

Одна вещь, которую я подумал сделать, это извлечь методы из клиентского класса и поместить их в сообщения. Я бы поместил метод, подобный process (), в интерфейс IMessage, а затем поместил поведение, специфичное для сообщения, в каждый из конкретных типов сообщений. Это сделает клиент простым, потому что он просто вызовет message.process (), а не проверяет типы. Однако единственная проблема заключается в том, что поведение, содержащееся в условных выражениях, связано с операциями с данными, содержащимися в классе Client. Таким образом, если бы я реализовал метод процесса в конкретных классах сообщений, мне пришлось бы передать его клиенту, и я тоже не знаю, имеет ли это смысл.

public class Client {
    messageReceived(IMessage message) {
        if(message instanceof concreteMessageA) {
            concreteMessageA msg = (concreteMessageA)message;
            //do concreteMessageA operations
        }
    }
        if (message instanceof concreteMessageB) {
            concreteMessageb msg = (concreteMessageB)message;
            //do concreteMessageB operations
        }
}

Ответы [ 6 ]

7 голосов
/ 10 августа 2009

Простой способ избежать экземпляра тестирования - полиморфная отправка; например,

public class Client {
    void messageReceived(IMessage message) {
        message.doOperations(this);
    }
}

, где каждый класс сообщений определяет соответствующий doOperations(Client client) метод.

РЕДАКТИРОВАТЬ: второе решение, которое лучше соответствует требованиям.

Альтернатива, которая заменяет последовательность тестов instanceof оператором switch:

public class Client {
    void messageReceived(IMessage message) {
        switch (message.getMessageType()) {
        case TYPE_A:
           // process type A
           break;
        case TYPE_B:
           ...
        }
    }
}

Каждый класс IMessage должен определить метод int getMessageType() для возврата соответствующего кода. Перечисления работают так же хорошо, как и целые, и более элегантны, ИМО.

4 голосов
/ 10 августа 2009

Одним из вариантов здесь является цепочка обработчиков . У вас есть цепочка обработчиков, каждый из которых может обработать сообщение (если применимо), а затем потребляет его, то есть оно не будет передаваться дальше по цепочке. Сначала вы определяете интерфейс Handler:

public interface Handler {
    void handle(IMessage msg);
}

И тогда логика цепочки обработчика выглядит так:

List<Handler> handlers = //...
for (Handler h : handlers) {
    if (!e.isConsumed()) h.handle(e);
}

Тогда каждый обработчик может решить обработать / использовать событие:

public class MessageAHandler implements Handler {
    public void handle(IMessage msg) {
        if (msg instanceof MessageA) {
            //process message
            //consume event 
            msg.consume();
        }
    }
}

Конечно, это не избавляет от instanceof s - но это означает, что у вас нет огромного if-elseif-else-if-instanceof блока, который может быть нечитаемым

1 голос
/ 10 августа 2009

Какой тип системы сообщений вы используете?

У многих есть опции для добавления фильтра к обработчикам на основе заголовка сообщения или содержимого. Если это поддерживается, вы просто создаете обработчик с фильтром, основанным на типе сообщения, тогда ваш код хорош и чист без необходимости в экземпляре или проверке типа (поскольку система обмена сообщениями уже проверила его для вас).

Я знаю, что вы можете сделать это в JMS или службе событий OSGi.

Поскольку вы используете JMS, вы можете сделать следующее, чтобы зарегистрировать своих слушателей. Это создаст слушателя для каждого уникального типа сообщения.

  String filterMsg1 = "JMSType='messageType1'";
  String filterMsg2 = "JMSType='messageType2'";

  // Create a receiver using this filter
  Receiver receiverType1 = session.createReceiver(queue, filterMsg1);
  Receiver receiverType2 = session.createReceiver(queue, filterMsg2);

  receiverType1.setMessageHandler(messageType1Handler);
  receiverType2.setMessageHandler(messageType2Handler);

Теперь каждый обработчик будет получать только определенный тип сообщения (без instanceof или if-then), при условии, конечно, что отправитель устанавливает тип через вызовы setJMSType () в исходящем сообщении.

Этот метод встроен в сообщение, но вы, конечно же, можете создать собственное свойство заголовка и отфильтровать его.

0 голосов
/ 04 апреля 2019

С JMS 2.0 вы можете использовать:

consumer.receiveBody(String.class)

Для получения дополнительной информации вы можете обратиться здесь :

0 голосов
/ 25 августа 2016

Решение Java 8, которое использует двойную диспетчеризацию. Не полностью избавляется от instanceof, но требует только одной проверки на сообщение вместо цепочки if-elseif.

public interface Message extends Consumer<Consumer<Message>> {};

public interface MessageA extends Message {

   @Override
   default void accept(Consumer<Message> consumer) {
      if(consumer instanceof MessageAReceiver){
        ((MessageAReceiver)consumer).accept(this);
      } else {
        Message.super.accept(this);
      }
   }
}

public interface MessageAReceiver extends Consumer<Message>{
   void accept(MessageA message);
}
0 голосов
/ 11 августа 2010
//Message.java

abstract class Message{
     public abstract void doOperations();
 }

//MessageA.java

 class MessageA extends Message{
    public void doOperations(){ 
         //do concreteMessageA operations ;
    }
 }

   //MessageB.java

class MessageB extends Message {
     public void doOperations(){ 
         //do concreteMessageB operations 
     }
}

//MessageExample.java

class MessageExample{
   public static void main(String[] args) {
        doSmth(new MessageA());
    }

    public static void doSmth(Message message) {
       message.doOperations() ;     

     }
}   
...