Який правильний спосіб моделювати цю діяльність у реальному світі, яка, здається, потребує кругових посилань в OOP?


24

Я боровся з проблемою в проекті Java про кругові посилання. Я намагаюся моделювати ситуацію в реальному світі, за якої, здається, що об'єкти, про які йдеться, взаємозалежні і потрібно знати один про одного.

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

Заняття:

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

  • Дошка: просте зображення ігрової дошки, яку можна доручити відображати рух.

  • MoveList: список Moves. Це подвійне призначення: вибір рухів, доступних у певній точці, або перелік рухів, які були зроблені в грі. Його можна розділити на два майже однакові класи, але це не стосується питання, яке я задаю, і може ускладнити його далі.

  • Переміщення: один хід. Він включає все про хід як список атомів: забрати шматок звідси, покласти його туди, вийняти звідти захоплений шматок.

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

Циркулярні посилання мають багато, наприклад: RuleBook повинен знати про ігровий стан, щоб визначити, які рухи доступні в даний момент часу, але держава гри повинна запитати RuleBook про початкову стартову схему та про те, які побічні ефекти супроводжують хід один раз це робиться (наприклад, хто рухається далі).

Я спробував організувати новий набір класів ієрархічно, з RuleBook вгорі, оскільки він повинен знати про все. Але це призводить до того, що потрібно перенести багато методів у клас RuleBook (наприклад, зробити крок), що робить його монолітним і не особливо репрезентативним щодо того, яким повинен бути RuleBook.

То який правильний спосіб це організувати? Чи варто перетворити RuleBook на BigClassThatDoesAlmostEverythingInTheGame, щоб уникнути кругових посилань, відмовившись від спроби точно моделювати гру в реальному світі? Або я повинен дотримуватися взаємозалежних класів і примушувати компілятора до компіляції їх якось, зберігаючи свою реальну модель? Або є якась явна дійсна структура, яка мені не вистачає?

Дякуємо за будь-яку допомогу, яку ви можете надати!


7
Що робити, якщо RuleBookвзяти, наприклад State, аргумент, і повернути дійсне MoveList, тобто "ось де ми зараз, що можна зробити далі?"
jonrsharpe

Що сказав @jonrsharpe Під час гри в справжню настільну гру правильник також не знає про будь-які фактичні ігри. Я, мабуть, навіть ввів ще один клас, щоб насправді обчислити ходи, але це може залежати від того, наскільки великий цей клас RuleBook вже є.
Себастіян ван ден Брук

4
Уникання об'єкта бога (BigClassThatDoesAlmostEverythingInTheGame) набагато важливіше, ніж уникати кругових посилань.
користувач281377

2
@ user281377, однак це не обов'язково взаємовиключні цілі!
jonrsharpe

1
Чи можете ви показати спроби моделювання? Наприклад, діаграма?
Користувач

Відповіді:


47

Я боровся з проблемою в проекті Java про кругові посилання.

Збірник сміття Java не покладається на техніку підрахунку еталонів. Кругові посилання не викликають жодних проблем у Java. Час, витрачений на усунення ідеально природних кругових посилань на Яві, витрачається даремно.

Я зашифрував це [...], але проблема полягає в тому, що він повний кругових посилань. Я реалізував це ще тоді, заповнивши всі переплетені класи в єдиний вихідний файл , [...]

Не Необхідно. Якщо ви просто компілюєте всі вихідні файли одразу (наприклад, javac *.java), компілятор без проблем усуне всі прямі посилання.

Або я повинен дотримуватися взаємозалежних класів і примушувати компілятор до компіляції їх якось, [...]

Так. Очікується, що класи додатків будуть взаємозалежними. Компіляція відразу всіх вихідних файлів Java, які належать до одного пакету, не є розумним злом, це точно так, як має працювати Java .


24
"Кругові посилання не викликають жодних проблем у Java." Що стосується складання, це правда. Хоча круглі посилання вважаються поганим дизайном .
Чоп

22
Циркулярні посилання є абсолютно природними у багатьох ситуаціях, тому Java та інші сучасні мови використовують складний збирач сміття замість простого лічильника посилань.
користувач281377

3
Можливість вирішення кругових посилань на Java - це чудово, і це, безумовно, правда, що вони є природними у багатьох ситуаціях. Але ОП представила конкретну ситуацію, і це слід врахувати. Заплутаний код спагетті, мабуть, не найкращий спосіб вирішити цю проблему.
Матвій читайте

3
Будь ласка, не поширюйте необгрунтовану FUD щодо незв'язаних мов програмування. Python підтримує GC цикли відліку з віків ( документи , також на SO: тут і тут ).
Крістіан Айхінгер

2
ІМХО ця відповідь є лише посередньою, оскільки немає жодного слова про кругові посилання, які можуть бути корисними, наприклад, в рамках ОП.
Док Браун

22

Зазначені кругові залежності - це сумнівна практика з дизайнерської точки зору, але вони не заборонені, і з суто технічної точки зору вони навіть не обов'язково є проблематичними , як ви вважаєте, що вони є: вони абсолютно законні в У більшості сценаріїв вони неминучі в деяких ситуаціях, а в деяких рідкісних випадках їх навіть можна вважати корисними.

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

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

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

Отже, важливо усвідомити та усунути це з того, що прагнення до мінімізації кругових залежностей - це пошуки чистоти дизайну, а не пошуки технічної коректності.

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

У конкретній ситуації, яка є у вас під рукою, мені здається, що вам потрібна нова сутність, можливо, яка називається "Гра" або "GameLogic", яку знають усі інші суб'єкти (без того, щоб хто-небудь з інших суб'єктів цього знав, ), щоб інші суб'єкти не мали знати один одного.

Наприклад, мені здається нерозумним, що вашій структурі RuleBook потрібно знати що-небудь про сутність GameState, оскільки книга правил - це те, з чим ми консультуємось для того, щоб грати. Це не те, що бере активну участь у грі. Отже, саме цій новій суті "Ігри" потрібно проконсультуватися як з правилом, так і зі станом гри, щоб визначити, які рухи доступні, і це усуне кругові залежності.

Тепер, я думаю, я можу здогадатися, у чому полягає ваша проблема при такому підході: кодування сутності "Гра" ігрово-агностичним способом буде дуже важким, тому ви, швидше за все, закінчитеся не лише одним, а двома суб'єкти, яким потрібно мати індивідуальні реалізації для кожного різного типу гри: "RuleBook" та "Game". Що, в свою чергу, перемагає мету мати в першу чергу сутність "RuleBook". Ну, все, що я можу сказати з цього приводу, це те, що, можливо, просто можливо, ваше первісне прагнення написати систему, яка може грати в різні ігри, може бути благородною, але, можливо, погано задуманою. Якби я був у вашому взутті, я б зосередився на використанні загального механізму відображення стану всіх різних ігор, а також загального механізму прийому користувачів для всіх цих ігор,


1
Спасибі Майку. Ви маєте рацію щодо недоліків ігрової сутності; зі старим кодом аплетів я зміг скласти нові ігри з трохи більше, ніж новий підклас RuleBook та відповідний графічний дизайн.
Даміян Уокер

10

Теорія ігор трактує ігри як перелік попередніх ходів (типи значень, включаючи того, хто в них грав) та функцію ValidMoves (попередні рухи)

Я б спробував дотримуватись цього шаблону для неігрової частини гри і розглядати такі речі, як налаштування дошки, як рухи.

то користувальницький інтерфейс може бути стандартним матеріалом OO з одним способом, що відповідає логіці


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

Розглянемо шахи. Ігри в шахи зазвичай записуються як списки ходів. http://en.wikipedia.org/wiki/Portable_Game_Notation

перелік рухів визначає повний стан гри набагато краще, ніж зображення дошки.

Скажімо, наприклад, ми починаємо створювати об'єкти для Board, Piece, Move тощо та такі методи, як Piece.GetValidMoves ()

спершу ми бачимо, що ми повинні мати посилання на дошку, але потім ми вважаємо, що закидати. що ви можете зробити лише в тому випадку, якщо ви ще не переїхали свого короля чи грака. Тож нам потрібен прапор MovedAlready на короля та граки. Аналогічно пішаки можуть пересунути 2 квадрати при першому ході.

Тоді ми бачимо, що при виправданні дійсний хід короля залежить від існування та стану грака, тому дошці потрібно мати на ньому шматки та посилатися на ці шматки. ми потрапляємо у вашу проблему кругової реф.

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

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

Якщо ми додамо рухи налаштування як рухи, 'від поля до квадрата X' і адаптуємо об’єкт «Правила», щоб зрозуміти цей хід, то ми все ще можемо представляти гру як послідовність рухів.

Аналогічно, якщо у вашій грі сама дошка не є статичною, скажімо, ми можемо додавати квадрати в шахи або видаляти квадрати з дошки, щоб вони не могли переміщуватися поперек. Ці зміни також можуть бути представлені як Moves, не змінюючи загальної структури двигуна Правил або не маючи посилання на об'єкт BoardSetup аналогічного


Це, як правило, ускладнить реалізацію ValidMoves, що сповільнить вашу логіку.
Taemyr

насправді, я припускаю, що налаштування плати є змінним, тому вам потрібно якось це визначити. Якщо конвертувати конфігураційне переміщення в якусь іншу структуру або об'єкт для полегшення обчислення, ви можете кешувати результат, якщо потрібно. У деяких іграх є дошки, які змінюються грою, а деякі дійсні рухи можуть залежати від попередніх кроків, а не від поточної позиції (наприклад, заміна в шахах)
Еван

1
Додавання прапорів і речей - це складність, якої ви уникаєте, просто переїхавши в історію переміщень. не дорого перебирати скажімо 100 шахових рухів, щоб отримати поточну настройку дошки, і ви можете кешувати результат між ходами
Еван

1
Ви також уникаєте змінити свою об'єктну модель, щоб вона відображала правила. тобто для шахів, якщо ви робите validMoves -> Piece + Board, вам не вдасться закинути, перейти в минуле, спершу рухатись пішаками та просуванням шматка, і вам доведеться додати додаткову інформацію до об'єктів або посилатися на третій об’єкт. Ви також втрачаєте уявлення про те, хто це їде, і такі поняття, як відкритий чек
Еван

1
@Gabe boardLayout- це функція всіх priorMoves(тобто, якби ми підтримували її як стан, нічого, крім кожного, не вносили б thisMove). Отже, пропозиція Евана по суті є "вирізати середню людину" - дійсні переміщення прямі функції, ніж усі попередні, а не validMoves( boardLayout( priorMoves ) ).
OJFord

8

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


6

Я вважаю, що циркулярні посилання та об'єкт бога у вашому випадку можна легко усунути, відокремивши управління потоком гри від стану та керувати моделями гри. Роблячи це, ви, ймовірно, отримаєте велику гнучкість і позбудетесь якоїсь непотрібної складності.

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

Об'єкт ігрового стану не повинен змінювати себе або усвідомлювати правила. Клас просто повинен забезпечити модель легко оброблюваної (створеної, перевіреної, зміненої, збереженої, журнальної, скопійованої, кешованої тощо) та ефективних об'єктів стану гри для решти програми.

У книзі з правилами не повинно знати і не поспішати з будь-якою грою, що триває Потрібно лише переглядати стан гри, щоб мати змогу визначити, які рухи є законними, і відповісти на результат гри потрібно лише тоді, коли його запитають, що відбувається, коли хода застосована до ігрового стану. Він також може забезпечити початковий стан гри, коли його запитають про початковий макет.

Контролер повинен знати про стан ігор та книгу з правилами, а також, можливо, і про деякі інші об'єкти ігрової моделі, але йому не слід возитися з деталями.


4
ТОЧНО моє мислення. ОП змішує надто багато даних і процедур в одних і тих же класах. Краще розділити їх більше. Це гарна розмова на цю тему. До речі, коли я читаю "перегляд в ігровий стан", я думаю, що "аргумент функції". +100, якби міг.
jpmc26

5

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

Почнемо з опису того, що робить кожен клас.

GameStateКлас повинен містити тільки інформацію про поточний стан гри. Він не повинен містити будь-якої інформації про те, які минулі стани гри чи які можливі майбутні рухи. Він повинен містити лише інформацію про те, які фігури є, на яких квадратах у шахах, чи скільки та який тип шашок, на яких точках у нардах. GameStateМіститиме деяку додаткову інформацію, як інформація про рокіровку в шахах або про подвоєнні куба в нарди.

MoveКлас трохи складніше. Я б сказав, що я можу вказати хід гри, вказавши, GameStateщо є результатом гри. Тож ви могли собі уявити, що такий крок можна просто здійснити як GameState. Однак, в ході (наприклад,) ви можете уявити, що набагато простіше вказати хід, вказавши одну точку на дошці. Ми хочемо, щоб наш 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 не потрібно "знати" про стан будь-якої конкретної гри, щоб виконати свою роботу; правила гри не змінюються залежно від поточного стану гри. Отже, або ви створили його неправильно, або ви створили його правильно, але пояснюєте його неправильно.


+1. Ви купуєте фізичну настільну гру, отримуєте книгу правил, яка здатна описати правила без стану.
unperson325680

1

Кругова залежність не обов'язково є технічною проблемою, але її слід вважати кодовим запахом, що, як правило, є порушенням Принципу єдиної відповідальності .

Ваша кругова залежність походить від того, що ви намагаєтесь зробити занадто багато від свого Stateоб'єкта.

Будь-який державний об'єкт повинен надавати лише методи, що безпосередньо стосуються управління цією місцевою державою. Якщо для цього потрібно щось більше, ніж найосновніша логіка, то, ймовірно, це слід розбити на більш широку схему. Деякі люди мають різні думки з цього приводу, але як загальне правило, якщо ви робите щось більше, ніж одержувачі та сетери на даних, ви робите занадто багато.

У цьому випадку вам краще мати папку StateFactory, яка могла б знати про Rulebook. Напевно, у вас є інший клас контролера, який використовує ваш StateFactoryдля створення нової гри. Stateточно не слід знати про це Rulebook. Rulebookможе знати про Stateзалежність від виконання ваших правил.


0

Чи є необхідність прив'язування об'єкта правила до певного ігрового стану, чи було б більше сенсу мати об’єкт книги з методом, який, даючи стан гри, повідомляє про те, які рухи доступні для цього стану (і, повідомивши про це, нічого не пам'ятаєте про державу, про яку йдеться)? Якщо немає чого отримати, якщо об'єкт, про який запитують про доступні рухи, зберігає пам'ять про стан гри, немає необхідності в ньому зберігати посилання.

Можливо, в деяких випадках є переваги, коли об'єкт, що оцінює правила, підтримує стан. Якщо ви думаєте, що може виникнути така ситуація, я б запропонував додати клас «арбітр», а з правилом надати метод «createReferee». На відміну від правила, який нічого не цікавить, невже його запитують про одну гру чи п’ятдесят, суддівський об’єкт розраховував би представити одну гру. Не можна було б зафіксувати всю ситуацію, пов’язану з грою, яку вона проводить, але вона може кешувати будь-яку інформацію про гру, яку вона вважала б корисною. Якщо гра підтримує функцію "скасувати", може бути корисним, щоб арбітр включив засоби створення об'єкта "знімок", який може зберігатися разом із попередніми станами гри; цей об'єкт повинен,

Якщо може виникнути необхідність ув'язування між правилами обробки та станом гри в коді, використання об'єкта арбітра дозволить зберегти таке з'єднання з головного каталогу правил та класів ігор стану. Також можливо, щоб нові правила розглядали аспекти ігрового стану, які клас ігрового стану не вважатиме відповідними (наприклад, якщо було додано правило, яке сказало: "Об'єкт X не може робити Y, якщо він коли-небудь знаходився в розташуванні Z" ", арбітр може бути змінений, щоб відслідковувати, які об'єкти були на місці Z, не змінюючи клас гри-стан).


-2

Правильний спосіб вирішення цього питання полягає у використанні інтерфейсів. Замість того, щоб два класи знали про інше, запропонуйте кожному класу реалізувати інтерфейс та посилання, що стосується іншого класу. Скажімо, у вас є клас A і клас B, на який потрібно посилатися в інший спосіб. Майте інтерфейс реалізації класу A та інтерфейс B класу B, тоді ви можете посилатись на інтерфейс B з класу A та інтерфейс A з класу B. Клас A може бути у власному проекті, як і у клас B. Інтерфейси є окремим проектом на які посилаються і інші проекти.


2
це, здається, лише повторює пункти, зроблені та пояснені у попередній відповіді, опублікованій за кілька годин до цього
гнат
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.