Вопросы философического дизайна для OOP-Tetris

Вы пишете программу Tetris на Java. Как бы вы создали свой дизайн класса в отношении следующих аспектов?

  • Класс Piece:. Имеет один класс Piece, с внутренним массивом, который определяет форму части, и имеет семь классов Piece, по одному для каждой части. Все они являются подклассами одного родового класса Piece.
  • Представление класса штук: Имеет массив из 4 экземпляров Block, представляющий один квадрат части, и каждый Block содержит свое местоположение на доске (в графических координатах) против. имеющий массив 4x4, где null означает, что там нет блока, а местоположение определяется формой массива.
  • Местоположение: Каждый Block в массиве Piece или в массиве Board сохраняет свое местоположение по сравнению с Piece и Board знает местоположения Blocks, которые содержат их.
  • Создание фрагмента: Имеет статический метод класса Piece getRandomPiece или имеет PieceFactory, который вы делаете одним экземпляром, который имеет метод genRandomPiece в экземпляре.
  • Манипуляция текущей частью: Используйте шаблон Proxy, так что все, что требует доступа к нему, просто использует прокси-сервер или имеет метод getCurrentPiece в классе Board и вызов что в любое время вы хотите что-то сделать с текущей частью.

Это не домашнее задание. Я просто расстроен тем, чему учит интро-курс CS в моем колледже, и я хочу посмотреть, что считают люди в целом. Что можно было бы назвать "хорошим" дизайном ООП? Игнорируйте тот факт, что это для вступительного курса - как бы вы это сделали?

Ответы

Ответ 1

Во-первых, я бы не подклассифицировал класс Piece, потому что он не нужен. Класс Piece должен быть способен описывать любую форму без использования наследования. ИМХО, это не то, что наследование было сделано, и это просто усложняет ситуацию.

Во-вторых, я не буду хранить координаты x/y в объектах Block, потому что он позволяет двум блокам существовать в одном и том же месте. Классы Piece сохраняли бы сетку (т.е. 2D-массив), удерживающую объекты блока. Координаты x/y были бы индексами 2D-массива.

Что касается статического метода vs factory для получения случайного фрагмента, я бы пошел с объектом factory для простого факта, что объект factory можно высмеять для тестирования.

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

Ответ 2

  • Один Piece интерфейс с семью классами, которые реализуют этот интерфейс для отдельных частей (что также позволит диску ООП обсуждать интерфейсы) ( EDIT: Один Piece. См. комментарии)
  • У меня был бы класс BlockGrid, который можно использовать для любой карты блоков - как на доске, так и на отдельных частях. BlockGrid должны иметь методы для обнаружения пересечений - например, boolean intersects(Block block2, Point location) - а также для поворота сетки (интересная точка обсуждения для курса: если Board не нужно вращать, следует использовать метод rotate() быть в BlockGrid?). Для Piece BlockGrid будет представлять собой сетку 4x4.
  • Я бы создал PieceFactory с помощью метода getRandomShape(), чтобы получить экземпляр одной из семи форм
  • Для управления куском я попал бы в архитектуру Model-View-Controller. Модель - это кусок. Контроллер, возможно, является PieceController, а также разрешает или запрещает правовые/незаконные действия. То, что показало бы Piece на экране, это PieceView (hrm, или это BlockGridView, который может показать Piece.getBlockGrid()? Еще одна точка обсуждения!)

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

EDIT: Claudiu помог мне понять, что BlockGrid будет достаточно различать фрагменты, поэтому нет необходимости в Piece с несколькими подклассами; скорее, экземпляр класса Piece может отличаться от других экземпляров на основе его BlockGrid.

Ответ 3

Все эти классы и прочее... это может сделать проблему слишком абстрактной для того, что она есть на самом деле. Множество разных способов представления фрагментов tetris (stackoverflow.com/questions/233850/...) и множество способов управления ими. Если бы это было для интро-курса, я бы не стал беспокоиться о ООП. Просто мое мнение, а не настоящий ответ на ваш вопрос.

Сказав это, достаточно было бы просто класс Board и Piece.

Класс платы. Инкапсулирует простой 2d-массив прямоугольников. Свойства, такие как currentpiece, nextpiece. Такие процедуры, как draw(), fullrows(), drop() и т.д., Которые манипулируют текущей частью и заполненными квадратами платы.

Класс Piece. Инкапсулирует массив беззнаковых 16-битных чисел, кодирующих фрагменты в разных поворотах. Вы будете отслеживать цвет, текущее местоположение и поворот. Возможно, потребуется одна рутина, rotate().

Остальное будет, в зависимости от среды, обрабатывать события клавиатуры и т.д.

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

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

Ответ 4

Класс Piece: Я думаю, что одного класса для всех частей достаточно. Функции класса shoudl достаточно общие для работы для любой части, поэтому нет необходимости в подклассе.

Представление класса штук: Я считаю, что массив 4x4, вероятно, лучший способ, так как после этого вам будет намного легче повернуть кусок.

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

Создание части: Честно говоря, для этого я не чувствую, что это сильно изменит ситуацию. Сказав это, я бы предпочел статическую функцию, поскольку для этой функции действительно не так много, что она гарантирует свой класс.

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

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