#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».