Как правильно моделировать эту реальную деятельность, которая, кажется, нуждается в циклических ссылках в ООП?

24

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

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

Классы:

  • RuleBook: объект, который может быть опрошен для таких вещей, как начальная раскладка доски, другая начальная информация о состоянии игры, например, кто двигается первым, ходы, которые доступны, что происходит с состоянием игры после предложенного хода, и оценка текущая или предлагаемая позиция совета.

  • Доска: простое представление игровой доски, на которой можно указать движение.

  • MoveList: список ходов. Это двойная цель: выбор доступных ходов в данный момент или список ходов, которые были сделаны в игре. Его можно разбить на два почти идентичных класса, но это не относится к вопросу, который я задаю, и может усложнить его.

  • Ход: один ход. Он включает в себя все, что касается движения, в виде списка атомов: возьмите отсюда кусок, положите его туда, удалите отснятый кусок.

  • Состояние: полная информация о состоянии игры. Не только позиция Правления, но и MoveList, и другая информация о состоянии, например, кто должен двигаться сейчас. В шахматах можно было бы записать, были ли перемещены король и ладьи каждого игрока.

Например, имеется множество циклических ссылок: RuleBook необходимо знать о состоянии игры, чтобы определить, какие ходы доступны в данный момент, но Game State должен запросить RuleBook для начального начального макета и каких побочных эффектов сопровождают ход один раз. это сделано (например, кто движется дальше).

Я попытался организовать новый набор классов иерархически, с RuleBook наверху, так как он должен знать обо всем. Но это приводит к тому, что необходимо переместить множество методов в класс RuleBook (например, сделать ход), что делает его монолитным и не особенно отражает, каким должен быть RuleBook.

Так как правильно организовать это? Должен ли я превратить RuleBook в BigClassThatDoesAlmostEverythingInTheGame, чтобы избежать циклических ссылок и отказаться от попытки смоделировать игру в реальном мире? Или я должен придерживаться взаимозависимых классов и заставить компилятор каким-то образом их компилировать, сохраняя мою модель реального мира? Или есть какая-то очевидная действительная структура, которую мне не хватает?

Спасибо за любую помощь, вы можете дать!

Дэмиан Уокер
источник
7
Что если, RuleBookнапример , взятьState в качестве аргумента аргумент и вернуть действительное значение MoveList, то есть «вот где мы сейчас находимся, что можно сделать дальше?»
Джоншарп
Что сказал @jonrsharpe. Играя в настоящую настольную игру, книга правил также не знает ни о каких реальных играх. Возможно, я бы даже ввел другой класс для вычисления ходов, но это может зависеть от того, насколько велик этот класс RuleBook.
Себастьян ван ден Брук
4
Избегать объекта бога (BigClassThatDoesAlmostEverythingInTheGame) гораздо важнее, чем избегать циклических ссылок.
user281377
2
@ user281377 это не обязательно взаимоисключающие цели!
Джоншарп
1
Можете ли вы показать попытки моделирования? Диаграмма для примера?
Пользователь

Ответы:

47

Я боролся с проблемой в проекте Java о циклических ссылках.

Сборщик мусора в Java не использует методы подсчета ссылок. Циркулярные ссылки не вызывают каких-либо проблем в Java. Время, потраченное на устранение совершенно естественных циклических ссылок в Java, - пустая трата времени.

Я закодировал это [...], но проблема в том, что он полон циклических ссылок. Я реализовал это тогда, поместив все переплетенные классы в один исходный файл , [...]

Не обязательно. Если вы просто скомпилируете все исходные файлы одновременно (например, javac *.java), компилятор разрешит все прямые ссылки без проблем.

Или я должен придерживаться взаимозависимых классов и заставить компилятор каким-то образом их компилировать, [...]

Да. Ожидается, что классы приложений будут взаимозависимыми. Компиляция всех исходных файлов Java, которые принадлежат одному и тому же пакету одновременно, не является умным взломом, это именно то, как Java должна работать.

Atsby
источник
24
«Циркулярные ссылки не вызывают каких-либо проблем в Java». С точки зрения компиляции это правда. Циркулярные ссылки считаются плохим дизайном .
Чоп
22
Циклические ссылки совершенно естественны во многих ситуациях, поэтому в Java и других современных языках вместо простого счетчика ссылок используется сложный сборщик мусора.
user281377
3
Java способна разрешать циклические ссылки - это прекрасно, и, безусловно, это правда, что они естественны во многих ситуациях. Но OP представил конкретную ситуацию, и это следует учитывать. Запутанный код спагетти, вероятно, не лучший способ справиться с этой проблемой.
Мэтью Читал
3
Пожалуйста, не распространяйте необоснованные FUD о несвязанных языках программирования. Python поддерживает GC эталонных циклов с давних времен ( документы , также на SO: здесь и здесь ).
Кристиан Айхингер,
2
ИМХО, этот ответ только посредственный, так как нет ни одного слова о том, что циклические ссылки полезны, например, для ФП.
Док Браун
22

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

На самом деле, существует очень немного сценариев, в которых java-компилятор будет отрицать циклическую зависимость. (Примечание: может быть больше, я могу думать только о следующем прямо сейчас.)

  1. В наследовании: у вас не может быть класса A, расширяющего класс B, который, в свою очередь, расширяет класс A, и совершенно разумно, что вы не можете иметь этого, поскольку альтернатива не имеет абсолютно никакого смысла с логической точки зрения.

  2. Среди локальных классов метода: классы, объявленные в методе, могут не ссылаться друг на друга. Вероятно, это не что иное, как ограничение java-компилятора, возможно, потому, что способность делать такие вещи недостаточно полезна, чтобы оправдать дополнительную сложность, которая должна идти в компилятор для ее поддержки. (Большинство Java-программистов даже не знают о том, что вы можете объявить класс в методе, не говоря уже о том, чтобы объявить несколько классов, и затем эти классы циклически ссылаются друг на друга.)

Поэтому важно осознать, что стремление минимизировать циклические зависимости - это стремление к чистоте проектирования, а не техническое исправление.

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

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

Например, мне кажется необоснованным, что ваша сущность RuleBook должна что-либо знать о сущности GameState, потому что книга правил - это то, с чем мы консультируемся, чтобы играть, а не то, что принимает активное участие в игре. Таким образом, именно эта новая сущность «Игра» должна обратиться к книге правил и состоянию игры, чтобы определить, какие ходы доступны, и это устраняет круговые зависимости.

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

Майк Накис
источник
1
Спасибо майк Вы правы насчет недостатков сущности Game; со старым кодом апплета я смог создать новые игры с чуть большим, чем новый подкласс RuleBook и соответствующий графический дизайн.
Дамиан Уокер
10

Теория игр рассматривает игры как список предыдущих ходов (типы значений, включая тех, кто в них играл) и функцию ValidMoves (previousMoves)

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

пользовательский интерфейс может быть стандартным ООП с одним способом ссылки на логику


Обновление для сокращения комментариев

Рассмотрим шахматы. Игры в шахматы обычно записываются в виде списков ходов. http://en.wikipedia.org/wiki/Portable_Game_Notation

Список ходов определяет полное состояние игры гораздо лучше, чем рисунок доски.

Скажем, например, мы начинаем создавать объекты для Board, Piece, Move и т. Д. И таких методов, как Piece.GetValidMoves ()

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

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

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

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

Если мы добавим установочные ходы как ходы «из прямоугольника в квадрат X» и адаптируем объект «Правила», чтобы понять этот ход, то мы все равно сможем представить игру как последовательность ходов.

Точно так же, если в вашей игре сама доска не является статичной, скажем, мы можем добавить квадраты в шахматы или удалить квадраты с доски, чтобы их нельзя было перемещать. Эти изменения также могут быть представлены как Move без изменения общей структуры вашего движка правил или необходимости ссылаться на объект BoardSetup аналогичного

Ewan
источник
Это может усложнить реализацию ValidMoves, что замедлит вашу логику.
Таемыр
не совсем, я предполагаю, что настройка платы является переменной, поэтому вам нужно как-то ее определить. Если вы конвертируете шаги установки в какую-либо другую структуру или объект, чтобы помочь вычислению, вы можете кэшировать результат, если это необходимо. В некоторых играх есть доски, которые меняются в зависимости от игры, и некоторые действительные ходы могут зависеть от предыдущих ходов, а не от текущей позиции (например, рокировка в шахматах)
Ewan
1
Добавление флагов и прочего - это сложность, которую вы избегаете, просто имея историю перемещений. это не дорого обходить, скажем, 100 шахматных ходов, чтобы получить текущую настройку доски, и вы можете кэшировать результат между ходами
Ewan
1
Вы также избегаете изменять свою объектную модель для отражения правил. то есть для шахмат, если вы делаете validMoves -> Piece + Board, вы проваливаете рокировку, en-passent, первый ход для пешек и продвижения фигуры и должны добавить дополнительную информацию к объектам или ссылаться на третий объект. Вы также теряете представление о том, кто идет, и такие понятия, как обнаруженная проверка
Ewan
1
@Gabe The boardLayoutявляется функцией всех priorMoves(то есть, если бы мы поддерживали его как состояние, ничто не было бы добавлено, кроме каждого thisMove). Следовательно, предложение Эвана, по сути, «разрезает посредника» - действительные движет прямую функцию всех предшествующих, а не validMoves( boardLayout( priorMoves ) ).
OJFord
8

Стандартный способ удаления циклической ссылки между двумя классами в объектно-ориентированном программировании состоит в том, чтобы ввести интерфейс, который затем может быть реализован одним из них. Таким образом, в вашем случае вы могли бы RuleBookсослаться на Stateкоторый затем ссылается на InitialPositionProvider(который будет интерфейсом, реализованным RuleBook). Это также облегчает тестирование, потому что вы можете создать объект, Stateкоторый использует другую (предположительно более простую) исходную позицию для целей тестирования.

Жюль
источник
6

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

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

Объект игрового состояния не должен изменять себя или знать правила. Классу просто нужно предоставить модель легко обрабатываемых (созданных, проверенных, измененных, сохраненных, зарегистрированных, скопированных, кэшированных и т. Д.) И эффективных объектов состояния игры для остальной части приложения.

Книга правил не должна знать или играть в какую-либо игру. Для того, чтобы определить, какие ходы являются законными, нужно только иметь представление о состоянии игры, и ему нужно ответить только с помощью итогового игрового состояния, когда его спросят, что происходит, когда ход применяется к игровому состоянию. Это может также обеспечить начальное игровое состояние, когда запрашивается начальная раскладка.

Контроллер должен знать о состоянии игры и книге правил и, возможно, о некоторых других объектах игровой модели, но он не должен вмешиваться в детали.

РОДОМ ИЗ
источник
4
Именно мое мышление. ОП смешивает слишком много данных и процедур в одних и тех же классах. Лучше разделить это больше. Это хороший разговор на эту тему. Кстати, когда я читаю «просмотр состояния игры», я думаю «аргумент функции». +100, если бы я мог.
jpmc26
5

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

Давайте начнем с описания того, что делает каждый класс.

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

MoveКласс немного сложнее. Я бы сказал, что могу указать ход для игры, указав GameStateрезультат, полученный в результате хода. Таким образом, вы можете представить, что движение может быть реализовано как GameState. Тем не менее, в go (например) вы можете представить, что намного проще указать ход, указав одну точку на доске. Мы хотим, чтобы наш Moveкласс был достаточно гибким, чтобы справиться с любым из этих случаев. Следовательно, Moveкласс на самом деле будет интерфейсом с методом, который выполняет предварительное перемещение GameStateи возвращает новое после перемещения GameState.

Теперь RuleBookкласс отвечает за знание всего о правилах. Это можно разбить на три вещи. Он должен знать, что такое начальный GameState, он должен знать, какие ходы законны, и он должен уметь определить, выиграл ли один из игроков.

Вы также можете создать GameHistoryкласс, чтобы отслеживать все шаги, которые были сделаны, и все, GameStatesчто произошло. Новый класс необходим, потому что мы решили, что один GameStateне должен нести ответственность за знание всего, GameStateчто было до него.

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

Первый есть GameState. Поскольку этот класс полностью зависит от конкретной игры, нет универсального Gamestateинтерфейса или класса.

Дальше есть Move. Как я уже сказал, это может быть представлено интерфейсом, который имеет единственный метод, который принимает состояние перед перемещением и создает состояние после перемещения. Вот код для этого интерфейса:

package boardgame;

/**
 *
 * @param <T> The type of GameState
 */
public interface Move<T> {

    T makeResultingState(T preMoveState) throws IllegalArgumentException;

}

Обратите внимание, что есть параметр типа. Это связано с тем, что, например, ChessMoveнеобходимо знать подробности предварительного хода ChessGameState. Так, например, объявление класса ChessMoveбудет

class ChessMove extends Move<ChessGameState>,

где вы уже определили ChessGameStateкласс.

Далее я буду обсуждать общий RuleBookкласс. Вот код:

package boardgame;

import java.util.List;

/**
 *
 * @param <T> The type of GameState
 */
public interface RuleBook<T> {

    T makeInitialState();

    List<Move<T>> makeMoveList(T gameState);

    StateEvaluation evaluateState(T gameState);

    boolean isMoveLegal(Move<T> move, T currentState);

}

Опять же, есть параметр типа для GameStateкласса. Поскольку RuleBookпредполагается, что оно знает начальное состояние, мы создали метод для определения начального состояния. Так как RuleBookпредполагается, что ходы являются законными, у нас есть методы, чтобы проверить, является ли ход законным в данном состоянии, и дать список законных ходов для данного состояния. Наконец, есть метод оценки GameState. Обратите внимание, что RuleBookответственность должна быть только за описание того, выиграл ли тот или иной игрок, но не за то, кто находится в лучшем положении в середине игры. Решение, кто находится в лучшем положении, является сложной вещью, которую следует перевести в свой класс. Следовательно, StateEvaluationкласс на самом деле представляет собой простое перечисление, заданное следующим образом:

package boardgame;

/**
 *
 */
public enum StateEvaluation {

    UNFINISHED,
    PLAYER_ONE_WINS,
    PLAYER_TWO_WINS,
    DRAW,
    ILLEGAL_STATE
}

Наконец, давайте опишем GameHistoryкласс. Этот класс отвечает за запоминание всех позиций, которые были достигнуты в игре, а также ходов, которые были сыграны. Главное, что он должен уметь делать - это записывать Moveкак проигранные. Вы также можете добавить функциональность для удаления Moves. У меня есть реализация ниже.

package boardgame;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @param <T> The type of GameState
 */
public class GameHistory<T> {

    private List<T> states;
    private List<Move<T>> moves;

    public GameHistory(T initialState) {
        states = new ArrayList<>();
        states.add(initialState);
        moves = new ArrayList<>();
    }

    void recordMove(Move<T> move) throws IllegalArgumentException {
        moves.add(move);
        states.add(move.makeResultingState(getMostRecentState()));
    }

    void resetToNthState(int n) {
        states = states.subList(0, n + 1);
        moves = moves.subList(0, n);
    }

    void undoLastMove() {
        resetToNthState(getNumberOfMoves() - 1);
    }

    T getMostRecentState() {
        return states.get(getNumberOfMoves());
    }

    T getStateAfterNthMove(int n) {
        return states.get(n + 1);
    }

    Move<T> getNthMove(int n) {
        return moves.get(n);
    }

    int getNumberOfMoves() {
        return moves.size();
    }

}

Наконец, мы могли бы представить себе Gameкласс, чтобы связать все вместе. GameПредполагается, что этот класс предоставляет методы, позволяющие людям увидеть, что это за ток GameState, увидеть, кто, если есть, посмотреть, какие ходы можно сыграть, и сыграть ход. У меня есть реализация ниже

package boardgame;

import java.util.List;

/**
 *
 * @author brian
 * @param <T> The type of GameState
 */
public class Game<T> {

    GameHistory<T> gameHistory;
    RuleBook<T> ruleBook;

    public Game(RuleBook<T> ruleBook) {
        this.ruleBook = ruleBook;
        final T initialState = ruleBook.makeInitialState();
        gameHistory = new GameHistory<>(initialState);
    }

    T getCurrentState() {
        return gameHistory.getMostRecentState();
    }

    List<Move<T>> getLegalMoves() {
        return ruleBook.makeMoveList(getCurrentState());
    }

    void doMove(Move<T> move) throws IllegalArgumentException {
        if (!ruleBook.isMoveLegal(move, getCurrentState())) {
            throw new IllegalArgumentException("Move is not legal in this position");
        }
        gameHistory.recordMove(move);
    }

    void undoMove() {
        gameHistory.undoLastMove();
    }

    StateEvaluation evaluateState() {
        return ruleBook.evaluateState(getCurrentState());
    }

}

Обратите внимание на этот класс, что RuleBookне несет ответственности за знание того, что ток GameState. Это GameHistoryработа. Таким образом, он Gameспрашивает, GameHistoryкаково текущее состояние, и дает эту информацию, RuleBookкогда Gameнужно сказать, каковы законные шаги или если кто-то победил.

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

Брайан Мотс
источник
3

По моему опыту, циклические ссылки обычно указывают на то, что ваш дизайн не продуман.

В вашем дизайне я не понимаю, почему RuleBook нужно «знать» о государстве. Конечно, он может получать состояние в качестве параметра некоторого метода, но зачем ему знать (т.е. хранить в качестве переменной экземпляра) ссылку на состояние? Это не имеет смысла для меня. RuleBook не нужно «знать» о состоянии какой-либо конкретной игры, чтобы выполнять свою работу; правила игры не меняются в зависимости от текущего состояния игры. Так что либо вы разработали это неправильно, либо вы разработали это правильно, но объясняете это неправильно.

Mehrdad
источник
+1. Вы покупаете физическую настольную игру, вы получаете книгу правил, которая может описывать правила без состояния.
unperson325680
1

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

Ваша круговая зависимость проистекает из того факта, что вы пытаетесь сделать слишком много из своего Stateобъекта.

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

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

00500005
источник
0

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

Возможно, в некоторых случаях было бы полезно поддерживать состояние объекта оценки правил. Если вы думаете, что такая ситуация может возникнуть, я бы предложил добавить класс "рефери", а в книге правил должен быть метод "createReferee". В отличие от книги правил, которая не заботится о том, спрашивается ли она об одной игре или пятидесяти, объект рефери должен был бы исполнять одну игру. Не следует ожидать, что он будет инкапсулировать все состояния, связанные с игрой, которую он исполняет, но может кэшировать любую информацию об игре, которую он сочтет полезной. Если игра поддерживает функциональность «отменить», может быть полезно, чтобы судья включил средство для создания объекта «снимка», который может быть сохранен вместе с более ранними состояниями игры; этот объект должен,

Если может потребоваться некоторая связь между аспектами кода, касающимися обработки правил и состояния игры, использование объекта-рефери позволит сохранить такую ​​связь вне основной книги правил и классов состояния игры. Это также может позволить новым правилам учитывать аспекты игрового состояния, которые класс игрового состояния не счел бы релевантными (например, если было добавлено правило, которое гласило: «Объект X не может делать Y, если он когда-либо находился в местоположении Z». msgstr "судья может быть изменен, чтобы отслеживать, какие объекты находились в точке Z, без необходимости изменять класс игрового состояния).

Supercat
источник
-2

Надлежащий способ справиться с этим - использовать интерфейсы. Вместо того, чтобы два класса знали друг о друге, пусть каждый класс реализует интерфейс и ссылается на другой класс. Допустим, у вас есть класс A и класс B, которые должны ссылаться друг на друга. Пусть класс A реализует интерфейс A, а класс B реализует интерфейс B, тогда вы можете ссылаться на интерфейс B из класса A и интерфейс A из класса B. Класс A может быть в своем собственном проекте, как и класс B. Интерфейсы находятся в отдельном проекте. на которые ссылаются оба других проекта.

Питер
источник
2
похоже, это просто повторяет высказанные и объясненные в предыдущем ответе замечания, опубликованные за несколько часов до этого
комнат