Переход от абстрактного общего к не абстрактному необщему

В последнее время я работаю над проектом на 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 вещи:

  1. Данные (связный график)
  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++ - хотя вы получаете некоторые гарантии исключений приведения типов, никогда не возникающих во время выполнения. Когда я использую это, я обычно пытаюсь использовать это для чего-то похожего на коллекции, и я пытаюсь взять их в качестве модели.

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

Другие вопросы по тегам