Переход от абстрактного общего к не абстрактному необщему
В последнее время я работаю над проектом на Java, который использует универсальный алгоритм поиска. Чтобы сделать сам алгоритм универсальным, я использовал несколько обобщений на всех классах, используемых в алгоритме.
Вот все объявления класса / интерфейса:
public class StateSearch<E extends AbstractState<E>>
public class AbstractState<E> implements State<E>
public class StateNode<E extends AbstractState<E>> implements Comparable<StateNode<E>>
public class StateQueue<E extends AbstractState<E>>
public interface State<E> extends ComparableState<E>>
Теперь в теории все это работает просто отлично. Однако, когда я применяю Алгоритм, я планирую, чтобы буква "Е" была своего рода игровым состоянием (скажем, "Доска для шашек", "Пасьянс", "Следопыт", "что у тебя"). Таким образом, я создал следующий класс:
public class GameState extends AbstractState<String>
Мое намерение для этой конкретной установки состоит в том, чтобы Game State был контейнером для String, который представляет состояние конкретной игры. Однако это вызывает проблему при попытке создания StateSearch с использованием следующего кода:
new StateSearch<GameState>(new GameState(initialState), new GameState(goalState));
Я получаю сообщение об ошибке Bound Mismatch, заявляя, что GameState
не является действительной заменой <E extends AbstractState<E>>
, Теперь я верю, что понимаю, почему это происходит. Мои мысли, потому что GameState
продолжается AbstractState<String>
, и не AbstractState<E>
,
К сожалению, я не хочу, чтобы мой GameState
Класс, чтобы иметь общий тип. Он предназначен для того, чтобы реализовывать что-то на самом деле, таким образом, переключаясь с Generics на Strings. Например, я могу захотеть создать GameState
Класс в совершенно другом проекте, который использует Integers для его реализации.
Учитывая методы, переменные и операции, которые я использую и выполняю в GameState
Класс, он не может быть универсальным.
Мой вопрос после всего этого: есть ли способ реализовать мой GameState
Класс как неуниверсальный класс таким образом, чтобы он удовлетворял ограничивающим требованиям, установленным моим StateSearch
Учебный класс?
Я бы не возражал, если бы мне пришлось изменить некоторые объявления классов. Дело в том, что мне нужно, чтобы алгоритм был универсальным, а реализация - не универсальной. GameState
Класс должен быть точкой, в которой я перехожу между ними.
Редактировать:
мне нужно
GameState
Класс, чтобы функционировать как реализация определенной игры. AbstractState
а также State
интерфейсы предназначены для того, чтобы позволить мне построить алгоритм поиска состояний, не требуя фактической реализации. Настройки таким образом позволяют мне применять эту настройку к нескольким играм.StateSearch
Класс в основном просто тянет штаты из моего StateQueue
, который является просто вектором AbstractState
Классы, упорядоченные системой приоритетов (отсюда и сопоставимая реализация). Способ расширения дерева состояний осуществляется с помощью getNextState()
функция от AbstractState
Учебный класс. Это нормально и все, но getNextState()
функция в основном захватывает лучшее состояние из моего getSuccessors()
функция, которая может быть определена только в GameState
Учебный класс. В другом месте это просто Аннотация.
Я не могу определить getSuccessors()
функционировать в общем виде, так как он основан исключительно на реализации. Эта функция предназначена для возврата всех возможных состояний из текущего состояния. Например, если игра была в крестики-нолики, getSuccessors()
Функция возвращает список игровых состояний, где каждое состояние представляет возможный ход.
GameState
Класс также содержит getEstimatedCost()
а также getRunningCost()
которые в основном действуют как функции h(x) и g(x) соответственно. Это также может быть определено только в GameState
Класс, так как оба зависят от реализации.
3 ответа
То, как вы определили вещи, является эффективной реализацией шаблона CURLYURURURING Template.
Когда у тебя есть:
public class StateSearch<E extends AbstractState<E>>
Это означает, что использовать GameState
как его общий тип, он должен быть определен как:
public class GameState extends AbstractState<GameState>
Что касается того, что вам действительно нужно, непонятно, зачем вам дженерики. Почему бы не GameState
интерфейса хватит?
Алгоритм поиска требует 2 вещи:
- Данные (связный график)
- Способ сравнения данных (узлы на графике)
Каждый узел знает о своих связанных узлах, представляющих граф. И узлы сравнимы между собой. Это удовлетворяет двум вещам, которые нужны алгоритму поиска.
Итак, в нашем дизайне для узлов вполне естественно передать эту информацию алгоритму поиска. Все остальные состояния узла должны быть скрыты от алгоритма поиска, чтобы соответствовать хорошим принципам ОО (инкапсуляция).
По этим причинам я предлагаю рефакторинг для этого более простого дизайна.
//Comparable interface satisfies measuring a node's value.
//getAdjacentNodes abstract method exposes graph structure.
abstract class StateNode implements Comparable<StateNode>{
abstract StateNode[] getAdjacentNodes();
}
//StateSearch can perform any search algorithm it needs StateNode.
class StateSearch<E extends StateNode>
//StateQueue is probably unnecessary. Consider replacing with a PriorityQueue.
class StateQueue<E extends StateNode> queue;
Будь StateNode
использует внутреннее состояние вообще - это решение реализующего кода. В приведенном ниже примере GameNode
действительно сохраняет состояние игры, но алгоритм поиска этого не знает. Все, что он знает, - это минимум, который ему нужно знать; какие StateNode
разоблачает. Если бы я полностью исключил состояние, алгоритм поиска не пострадал бы.
public class GameNode extends StateNode{
String state;
public GameNode(String state){
this.state = state;
}
public StateNode[] getAdjacentNodes(){
//return adjacent nodes.
}
public String getGameState(){
return state;
}
public int compareTo(StateNode other){
//comparison code
}
}
Для меня механизм Java Generic обеспечивает большую часть его преимуществ, делая возможной библиотеку коллекций. Похоже, что он разработан против такого варианта использования - что вполне оправдывает его - и он только управляет этим. Если вы посмотрите на реализацию приведения коллекции, вы, вероятно, увидите либо предупреждения компиляции, либо аннотации для отключения предупреждений из-за невозможности создания универсальных массивов. Обратите внимание, что, поскольку он работает по типу стирания, а не по генерации кода, он гарантирует отсутствие раздувания кода, но не обеспечивает дополнительной эффективности, как в C++ - хотя вы получаете некоторые гарантии исключений приведения типов, никогда не возникающих во время выполнения. Когда я использую это, я обычно пытаюсь использовать это для чего-то похожего на коллекции, и я пытаюсь взять их в качестве модели.
Я подозреваю, что невозможно сделать все, что вы хотите, с помощью дженериков, и я бы с осторожностью пытался сделать слишком много - как и с любой другой языковой функцией, которой вы не пользуетесь достаточно регулярно, чтобы овладеть уровнем рефлексивного уровня. Материал, который занимает у вас час, чтобы потренироваться сейчас, займет у вас гораздо больше времени, чтобы понять, когда вы вернетесь, чтобы изменить или расширить этот код позже.