Как унаследовать циклические обобщения?

#java #generics

#java #дженерики

Вопрос:

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

Я хотел использовать обобщения Java, чтобы каждая реализация не только реализовывала соответствующие интерфейсы надлежащим OO-способом, но также имела доступный и строго типизированный конкретный класс для специфичных для игры требований (без приведений и т.д.).

Например, a CheckersPlayer будет расширяться Player CheckersPlayerImpl реализует CheckersPlayer ), CheckersGame будет расширяться Game CheckersGameImpl внедрением CheckersGame ) и CheckersGameTable расширением GameTable и т.д.

 public interface Player
{
...
}

public Game<T extends GameTable, P extends Player>
{
...
   public T getTable();

   public List<P> getPlayers();
} 

public interface GameFactory<T extends GameTable, P extends Player, G extends Game>
{
   public G getGameInstance(T table, List<P> players)
} 

public interface GameTable<P extends Player, G extends Game, GF extends GameFactory>
{
...
   public G getCurrentGame();

   public List<P> getPlayers();
}
  

Самое приятное в этом то, что, например, CheckersGame тогда был бы объявлен как:

    public interface CheckersGame extends Game<CheckersTable, CheckersPlayer>
   {
     public CheckersTable getTable();

     public List<CheckersPlayer> getPlayers();
   }
  

Пока все хорошо.

У каждого GameTable на самом деле много общего, и я решил использовать шаблон Template, чтобы поместить некоторую общую логику алгоритма в абстрактную реализацию, а затем соответствующий конкретный класс, расширяющий его, просто «заполнил бы пробелы» материалом, специфичным для игры.

Однако теперь я получаю всевозможные ошибки «границ» в моих обобщениях.

My AbstractTable объявляется следующим образом:

 public abstract class AbstractTable<P extends Player, G extends Game, GF extends GameFactory> implements GameTable<P,G,GF>
{      
  GF gameFactory; 

  ...
  public final void startGame()
  {
    //the following line does not work
    G newGame = gameFactory.getGameInstance(this, getPlayers());
  }

}
  

Строка с заводским методом по какой-то причине не работает и возвращает Game not G .
Итак, я попытался изменить объявление класса, чтобы оно также было более конкретным в отношении типов GameFactory универсального типа:

   public abstract class AbstractTable<P extends Player, G extends Game, GF extends GameFactory<? extends GameTable, P, G> 
        implements GameTable<P,G,GF>
  

Но теперь я получил несоответствие типа между передаваемым типом ( AbstractTable<P,G,GF> ) и ожидаемым типом ? extends GameTable .

Итак, я попытался изменить фабрику следующим образом:

 public interface GameFactory<T extends GameTable<P, G, ? extends GameFactory>, P extends Player, G extends Game>
  

Что все еще не сработало.

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

Есть ли какой-нибудь способ сделать это правильно? К сожалению, мне нужны как конкретные GameTable , так и Game типы, чтобы знать друг о друге, поскольку один должен ссылаться на другой.

Ответ №1:

Вы смешали дженерики с не-дженериками, и это, как правило, приводит к путанице. В Game вы присвоили ей два общих типа параметров, но в GameFactory он рассматривается как неродовой. Попробуйте последовательно сделать все ваши типы либо универсальными, либо не-универсальными, и это должно сработать.

например

 public interface GameFactory<T extends GameTable, P extends Player, G extends Game<T,P>> {
    public G getGameInstance(T table, List<P> players)
} 
  

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

 public interface GameFactory<T extends GameTable, P extends Player> {
    public Game<T,P> getGameInstance(T table, List<P> players)
} 
  

Комментарии:

1. Я хочу иметь возможность расширить общий интерфейс GameFactory за счет более специфичного GameFactory, который по-прежнему будет интерфейсом. Поэтому я бы предпочел первый случай. Итак, GameTable в вашем примере не был сгенерирован? И как бы вы объявили AbstractTable? Спасибо.

2. @jbx, я понимаю твою проблему. Я думаю, вы рискуете сделать это настолько универсальным, что это не имеет смысла. Возможно, здесь стоит рассмотреть какой-нибудь дизайн YAGNI. 😉 en.wikipedia.org/wiki/You_ain’t_gonna_need_it

3. Хорошо, что я сделал, чтобы обойти эту проблему, так это то, что я удалил ссылку GameFactory на GameTable все вместе, чтобы избежать циклической генерации. Итак, GameFactory<P расширяет Player, G расширяет Game>, а сам метод просто принимает общедоступную G getGameInstance(таблицу игр, список<P> игроков). В фактическую реализацию concrete factory я вставил instanceof для проверки в реальном времени (хорошо, это не время компиляции, но аккуратно и локализовано) и зависит от его реализации, поэтому тот, кто реализует конкретную фабрику, может проверить, если захочет.

Ответ №2:

Вам действительно нужна эта игра с зависимостями <-> Game? Недостаточно, чтобы GameTable извлекал экземпляр игры из GameFactory.getGameInstance(), используя перечисление, указывающее тип таблицы? В основном я попытаюсь уменьшить зависимости между интерфейсами и классами.

Комментарии:

1. Нет, помните, что это фреймворк, который не будет знать о реализующих его классах. Потенциально они могут быть в другом файле JAR или реализованы кем-то другим. Итак, наличие перечисления — это не вариант. Чего я пытаюсь достичь здесь, так это строгой типизации между связанными конкретными классами Game, GameFactory, Player и GameTable

Ответ №3:

Проблема в том, что ваш GameFactory возвращает «G extends Game», и вы пытаетесь присвоить это другому «G extends Game», который потенциально не относится к тому же классу (хотя на самом деле он есть в вашей реализации).

Одним из способов решения этой проблемы было бы, чтобы GameFactory возвращал «Game», а не «что-то, что расширяет Game».