Как избежать ссылки на круглые единицы?

Представьте себе следующие два класса шахматной игры:

TChessBoard = class
private
  FBoard : array [1..8, 1..8] of TChessPiece;
...
end;

TChessPiece = class abstract
public
   procedure GetMoveTargets (BoardPos : TPoint; Board : TChessBoard; MoveTargetList : TList <TPoint>);
...
end;

Я хочу, чтобы два класса были определены в двух отдельных единицах ChessBoard.pas и ChessPiece.pas.

Как я могу избежать ссылки на круговую единицу, на которой я здесь нахожусь (каждый блок необходим в другой секции интерфейса устройства)?

Ответы

Ответ 1

Измените устройство, которое определяет TChessPiece, чтобы выглядеть следующим образом:

TYPE
  tBaseChessBoard = class;

  TChessPiece = class
    procedure GetMoveTargets (BoardPos : TPoint; Board : TBaseChessBoard; ...    
  ...
  end;    

затем измените блок, который определяет TChessBoard, чтобы выглядеть следующим образом:

USES
  unit_containing_tBaseChessboard;

TYPE
  TChessBoard = class(tBaseChessBoard)
  private
    FBoard : array [1..8, 1..8] of TChessPiece;
  ...
  end;  

Это позволяет передавать конкретные экземпляры шахматной фигуре, не беспокоясь о круговой ссылке. Поскольку плата использует Tchesspieces в ее частной, она действительно не должна существовать до декларации Tchesspiece, как владелец места. Любые переменные состояния, о которых должен знать, конечно, tChessPiece, должны быть размещены в tBaseChessBoard, где они будут доступны для обоих.

Ответ 2

Единицы Delphi не "фундаментально нарушены". Способ, которым они работают, облегчает феноменальную скорость компилятора и способствует созданию чистых классов.

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

В этом случае я сразу заметил идиосинхронность в дизайне класса, возникающую из этой круглой дилеммы.

То есть, почему у части всегда есть необходимость ссылаться на доску?

Если часть взята с доски, такая ссылка тогда не имеет смысла, или, возможно, действительные "MoveTargets" для удаленной части - это только те, которые действительны для этой части как "стартовая позиция" в новой игре? Но я не думаю, что это имеет смысл как нечто иное, кроме произвольного оправдания для случая, требующего, чтобы GetMoveTargets поддерживал вызов с помощью ссылки на NIL.

Конкретное размещение отдельной пьесы в любой момент времени является собственностью шахматной игры . В равной степени перемещение VALID, которое может быть ВОЗМОЖНО для любой данной части, зависит от размещения ДРУГИЕ фигуры в игре.

TChessPiece.GetMoveTargets не нуждается в знании текущего состояния игры. Это ответственность за TChessGame. И TChessPiece не нуждается в ссылке на игру или на доску, чтобы определить действительные цели перемещения из данной текущей позиции. Ограничения платы (8 рангов и файлов) - это константы домена, а не свойства заданного экземпляра платы.

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

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

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

Я бы подошел к этому, просто разрешив классу пешки указать все ВОЗМОЖНЫЕ цели перемещения - 1 или 2 квадрата вперед и обе диагонально вперед. TChessGame затем определяет, какая из них действительна по ссылке на занятие этих целей перемещения и состояния игры. 2 квадрата вперед возможны только в том случае, если пешка находится на домашнем ранге, а прямые квадраты занимают BLOCK. Move = недействительный мишень, UNOcupied диагональные квадраты FACILITATE, а если какой-либо другой действительный ход выставляет короля, то этот ход также недействителен.

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

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

Как и в 99% таких ситуаций (ime - ymmv), дилемма, пожалуй, лучше решается путем изменения дизайна класса, чтобы лучше представить моделируемую проблему, а не находить способ обучить дизайн класса произвольному файлу организация.

Ответ 3

Одним из решений может быть введение третьего модуля, который содержит декларации интерфейса (IBoard и IPiece).

Затем секции интерфейса двух блоков с объявлениями классов могут ссылаться на другой класс по интерфейсу:

TChessBoard = class(TInterfacedObject, IBoard)
private
  FBoard : array [1..8, 1..8] of IPiece;
...
end;

и

TChessPiece = class abstract(TInterfacedObject, IPiece)
public
   procedure GetMoveTargets (BoardPos: TPoint; const Board: IBoard; 
     MoveTargetList: TList <TPoint>);
...
end;

(Модификатор const в GetMoveTargets избегает ненужного подсчета ссылок)

Ответ 4

Лучше переместить класс ChessPiece в блок ChessBoard.
Если по какой-то причине вы не можете, попробуйте поместить одно предложение использования в часть реализации в одном блоке и оставить другой в части интерфейса.

Ответ 5

С Delphi Prism вы можете распространять свои пространства имен по отдельным файлам, чтобы там вы могли решить его чистым способом.

Принципы работы модулей просто принципиально нарушены с их текущей реализацией Delphi. Просто посмотрите, как "db.pas" необходимо иметь TField, TDataset, TParam и т.д. В одном чудовищном файле .pas, потому что их интерфейсы ссылаются друг на друга.

Во всяком случае, вы всегда можете переместить код в отдельный файл и включить его с помощью {$include ChessBoard_impl.inc}, например. Таким образом, вы можете разделить материал поверх файлов и иметь отдельные версии через ваш vcs. Тем не менее, это просто немного неудобно редактировать файлы таким образом.

Лучшим долгосрочным решением было бы побудить embarcadero отказаться от некоторых идей, которые имели смысл в 1970 году, когда родился паскаль, но это не намного больше, чем боль в заднице для разработчиков в наши дни. Однопроходным компилятором является один из них.

Ответ 6

Это не похоже на TChessBoard.FBoard должен быть массив TChessPiece, он может также быть TObject и быть downcasted в ChessPiece.pas.

Ответ 7

Другой подход:

Сделайте свою доску tBaseChessPiece. Он абстрактный, но содержит определения, которые вам нужно найти.

Внутренние работы находятся в tChessPiece, который происходит от tBaseChessPiece.

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

Ответ 8

как насчет этого подхода:

блок шахматной доски:

TBaseChessPiece = class 

public

   procedure GetMoveTargets (BoardPos : TPoint; Board : TChessBoard; MoveTargetList : TList <TPoint>); virtual; abstract;

...

TChessBoard = class
private
  FBoard : array [1..8, 1..8] of TChessPiece;

  procedure InitializePiecesWithDesiredClass;
...

единица единиц измерения:

TYourPiece = class TBaseChessPiece

public 

   procedure GetMoveTargets (BoardPos : TPoint; Board : TChessBoard; MoveTargetList : TList <TPoint>);override;

...

В этом блоке шахматной доски aproach будет включаться ссылка единицы единиц только в секцию реализации (из-за метода, который будет фактически создавать объекты), а единица единиц будет иметь ссылку на блок шахматной доски в интерфейсе. Если я не ошибаюсь, справляюсь с вашей проблемой простым способом...

Ответ 9

Вывести TChessBoard из TObject

TChessBoard = класс (TObject)

то вы можете объявить процедура GetMoveTargets (BoardPos: TPoint; Board: TObject; MoveTargetList: TList);

когда вы вызываете proc, используйте SELF в качестве объекта Board (если вы его вызываете оттуда), вы можете ссылаться на него с помощью

(Правление как TChessBoard). и получить доступ к свойствам и т.д. из этого.