Java Generics: использовать generi c class как параметр типа другого generi c class - PullRequest
1 голос
/ 04 августа 2020

Есть ли способ в Java использовать общий c класс в качестве параметра типа другого generi c класса?

Например, у меня есть Message и MessageBus как это:

public interface Message<V> {};

public interface MessageBus {
    <V> V execute(Message<V> message);
}

Теперь он работает хорошо.

И затем я получаю Command и CommandBus вот так:

public interface Command<V> {};

public interface CommandBus {
    <V> V execute(Command<V> command);
}

Работает хорошо, тоже.

Но реализация CommandBus почти такая же, как MessageBus. Я хочу определить MessageBus как общий c класс следующим образом:

public interface Message<V> {};
public interface MessageBus<T extends Message> {};
public interface Command<V> extends Message<V> {};
public interface CommandBus extends MessageBus<Command> {};

Кажется, все в порядке, но проблема возникает, когда я хочу определить метод execute:

public interface MessageBus<T extends Message> {
    <V> V execute(T<V> message);       // compiles error, T<V> is invalid
    <V> V execute(T message);          // T means Message<Object>, not Message<V>
    <V> V execute(Message<V> message); // CommandBus cannot override Message<V> to Command<V> in subclass
}

Обратите внимание, что V - это разные типы для каждого вызова, поэтому я не могу определить MessageBus следующим образом:

public interface MessageBus<V, T extends Message<V>> {
    /**
     * In this way, each bus can have only one fixed return type.
     * But different return type is expected on different message,
     * just like the first interface above.
     */
    V execute(T message);
}

Я хочу использовать их так:

public class Command1 implements Command<Integer> {};
public class Command2 implements Command<String> {};
public class Command3 implements Command<String> {};
public class Message1 implements Message<String> {};

CommandBus commandBus;
Integer v1 = commandBus.execute(new Command1());  // fine
String v2 = commandBus.execute(new Command2());   // fine
String v3 = commandBus.execute(new Command3());   // fine
String v4 = commandBus.execute(new Command1());   // should compile error, return type mismatch
String v5 = commandBus.execute(new Message1());   // should compile error, only support Command

У меня будут типы MessageBus, например CommandBus extends MessageBus<Command>, QueryBus extends MessageBus<Query>, EventBus extends MessageBus<Event> и т. Д.

Как мне реализовать это поведение?

Спасибо.

Ответы [ 4 ]

0 голосов
/ 07 августа 2020

CommandBus IS Особый MessageBus«

Рассмотрим следующую структуру классов. MessageBus можно представить как Animal, CommandBus как Cat, Message аналогично Food и Candy аналогично Command

 +----------------+
 |     Animal     |
 +----------------+                +----------------+
 |eat(Food)       |                |      Food      |
 |                                 |                |
 |                |                +--------^-------+
 +-------^--------+                         |
         |                                  |
         |                      >-----------------------<
 +----------------+             |                       |
 |      Cat       |    +----------------+      +----------------+
 +----------------+    |      Candy     |      |      Grass     |
 |eat(Food)       |    |                |      |                |
 |eat(Candy)      |    +----------------+      +----------------+
 |eat(Grass)      |
 +----------------+



Все, что « IS A Animal «абсолютно ДОЛЖЕН « eat(Food) ». Если он не ест пищу, то это НЕ и Animal. Вот что такое наследование.

Аналогично: все, что « IS A MessageBus» абсолютно ДОЛЖНО execute(Message)“. Если он не выполняет сообщение, то это НЕ a MessageBus.

Сказать: « A Командная шина должна отправлять только команды, никаких других сообщений. »в точности похож на высказывание:« A Cat должен есть только Candy, никаких других Food ». Если он не ест Food, то это НЕ и Animal.

Конечно , a Cat банка съесть Candy и Grass под специализированный обстоятельства. Это не поведение по умолчанию Cat's.

«… Я бы хотел наследование и дженерики… «

Возможно и то, и другое. Но я думаю, что ожидания , которые вы имеете для их объединения, не возможны.

Если вы действительно хотите наследования, тогда вы должны принять это Cat ОБЯЗАТЕЛЬНО eat(Food). Если вам нужен CommandBus, который не execute(Message), тогда вы не хотите наследования.

Конечно, можно реализовать CommandBus на execute(Command). Но это должно быть в добавлении к execute(Message) Из-за наследования , execute(Message) - поведение по умолчанию для CommandBus.

0 голосов
/ 04 августа 2020

… Как мне реализовать это поведение?…

Вот как я бы это реализовал

public interface MessageBus< S extends Foo< ? > > {

    < T extends Foo< U >, U > U execute( T message );
}

Я представил интерфейс Foo, который вы видите здесь, потому что для того, чтобы иметь возможность делать то, что вы говорите, вы хотите сделать - и делайте это в a тип безопасный способ - Message и Command должны быть одинаковыми тип .

В контексте этот типичный пример , Foo берет на себя ответственность за эту необходимую общность. Вы бы переименовали Foo, чтобы сделать что-то более значимое в вашей реальной бизнес-сфере.

« ... Я хочу использовать их вот так : «

   ...
   public class Command1 implements Command<Integer> {};
   public class Command2 implements Command<String> {};
   public class Command3 implements Command<String> {};
   public class Message1 implements Message<String> {};
   ...

В моей демонстрации Я подтвердил, что решение можно использовать вот так…

    CommandBus commandBus = new StandardCmdBus() ;
    
    CharSequence v0 = commandBus.execute(new CharSeqCommand());   
    
    Integer v1 = commandBus.execute(new IntegerCommand());    // fine
    
    String v2 = commandBus.execute(new StringCommand());      // fine    
    
    String v3 = commandBus.execute(new StringMessage());      // fine
    
    /*String v4 = commandBus.execute(new CharSeqCommand());*/ // error: incompatible types: inference variable U has incompatible bounds

Для бетона Messages и Commands, которые я также представил, это печатается как stdout

 Hail Bopp!
        666
Heal Setan!
Hell Santa!
0 голосов
/ 05 августа 2020

Вы уже знаете, из различных ошибок компиляции, которые были у вас изначально, ваша исходная реализация не может делать то, что вы сейчас выяснили, это то, что вы действительно хотите делать. В комментариях к своему ответу @daniu называет одну причину, почему это так.

Ваш исходный код хочет сделать то, что язык запрещает . А именно: Переопределите метод, используя сигнатуру, которая полностью отличается от супер-сигнатуры .

Таким образом, вы не хотите структурировать свое приложение так, как проиллюстрирован код в исходном вопросе. Чтобы сделать то, что вы говорите, что вы на самом деле хотите сделать, ваш исходный код нуждается в некоторых существенных рефакторах. Как и код в моем первом ответе. Отсюда этот отдельный ответ ...

public interface MessageBus< V > { 
    
    V evaluate( Message<V> msg );
}

public interface CommandBus< V > { 
    
    V evaluate( Command<V> msg );
}

public interface EventBus< V > { 
    
    V evaluate( Event<V> msg );
}
...
public interface Message < V >{ 
    
    V evaluate( );
}
public interface Command< V > extends Message< V >{ 
    
    V evaluate( );
}
...
public class StandardCmdBus { 
    
    public static <S, T extends Command<S>> S evaluate( T cmd ){ return cmd.evaluate();  }
}

Я демонстрирую, как это можно было бы использовать вот так ...

public static void main(String args[]) {
  
    Stream< Command< ? > > shellCmds = of( () -> 3.14, () -> "javac", () -> "gcc", () -> 42.424242D, () -> "grep", () -> 666);
    
    Stream< Command< ? > > robotCmds = of( () -> "Assemble 666,666,666,666 Cars!", () -> "Exterminate!", () -> "Disallow: /questions/63242515/", () -> 777.9311, () -> "Rescue Will Robinson!", () -> "Kill all humans!", () -> 666);
    
    Stream< Message< ? > > msgs = of( () -> "What hath God wrought?", () -> "Sending out an S.O.S...", () -> "I like apples...", () -> 666, () -> "Friends of space, how are you all? Have you eaten yet? Come visit us if you have time.", () -> "?.?.?", () -> "The answer is...", () -> 42);

    Stream< Event< ? > > evts = of( () -> "The Big Bang", () -> "First Contact", () -> 867.5309, () -> "The Moon Landing", () -> "onLoad()", () -> 666, () -> "The Rapture" );
    
    Stream< Query< ? > > queries = of( () -> "The Ultimate Query...", () -> 42 );
    
    CommandBus< ? > cmdBus = ( cmd ) -> cmd.evaluate( );
    
    MessageBus< ? > msgBus = ( msg ) -> msg.evaluate( );
    
    EventBus< ? > evtBus = ( evt ) -> evt.evaluate( );
    
    QueryBus< ? > qBus = ( q ) -> q.evaluate( );
    
    /* Totally type safe; no unchecked warnings (i.e. no casts invovled) */
    robotCmds.map( StandardCmdBus::evaluate ).forEach( out::println );
    
    /* Less type safe; But totally fine; „uses unchecked or unsafe opertions“ (i.e. casts invovled) */
    use( shellCmds, cmdBus::evaluate ); 
    
    use( msgs, msgBus::evaluate ); 
    
    use( evts, evtBus::evaluate );
    
    use( queries, qBus::evaluate );
    
    Message< String > wtf = ( ) -> "// should compile error";
    
    /* cmdBus.evaluate( wtf );*/ /* error: incompatible types: Message<String> cannot be converted to Command<CAP#1> */ 
    
}

... Что выводит на печать …

Assemble 666,666,666,666 Cars!
Exterminate!
Disallow: /questions/63242515/
777.9311
Rescue Will Robinson!
Kill all humans!
666
...
-----------

What hath God wrought?
Sending out an S.O.S...
I like apples...
666
Friends of space, how are you all? Have you eaten yet? Come visit us if you have time.
?.?.?
The answer is...
42        
...
-----------

The Ultimate Query...
42

...

Ваша исходная реализация слишком рьяно использует Generics. В какой-то момент мы все виноваты в этом. Это классная форма полиморфизма. Но при неправильном использовании они создают больше проблем, чем решают.

0 голосов
/ 04 августа 2020

Вы потеряли T от Message, который был исходным типом возврата.

public interface Message<T> {};
public interface MessageBus<T> {};
public interface Command<T> extends Message<T> {};
public interface CommandBus<T> extends MessageBus<T> {};

Теперь ваши классы будут похожи на

class MessageBus<String> {
    String execute(Message<String> m) { ... }
}
class CommandBus<Integer> {
    String execute(Message<Integer> m) { ... }
    Integer execute(Command<Integer> c) { ... }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...