Как лучше всего применить принципы ООП к играм и другим приложениям с графическим интерфейсом, управляемым вводом?

Всякий раз, когда я пытаюсь написать графические программы (будь то игра или любое приложение с графическим интерфейсом), я всегда получаю один или два класса богов со слишком большим количеством методов (и длинных методов тоже), и каждый класс имеет слишком много обязанностей. У меня графика выполняется одновременно с вычислениями и логикой, и я чувствую, что это действительно плохой способ организации моего кода. Я хочу научиться лучше организовывать свой код и распределять обязанности между разными классами. Вот пример того, с чего я хотел бы начать - я хочу написать клон Minesweeper, просто в качестве практики и попытаться улучшить свои навыки разработки программного обеспечения. Как мне сделать это красивым и объектно-ориентированным? Ради обсуждения, давайте просто скажем, что я использую Java (потому что я, вероятно, буду использовать либо это, либо C#). Вот некоторые вещи, о которых я бы подумал:

  • должна ли каждая плитка наследоваться от JButton или JComponent и обрабатывать отрисовку самостоятельно?
  • или плитки следует просто хранить как неграфический объект MinesweeperTile, а какой-то другой класс обрабатывает их?
  • является ли таймер обратного отсчета с 8-сегментным дисплеем (по крайней мере, до Vista) отдельным классом, который обрабатывает само рисование?
  • Когда пользователь щелкает, есть ли у плиток прослушиватели событий мыши или какой-то другой метод обнаружения столкновений перебирает плитки и проверяет каждую, чтобы увидеть, не попал ли в нее?

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


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


edit2: Спасибо всем, кто ответил. Хотел бы я принять более одного ответа ..

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
1 915
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

используйте платформу MVC, которая выполняет всю сложную организационную работу за вас. на SO есть масса тем о фреймворках MVC.

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

MVC обеспечивает четкое разделение проблем; не смотри дальше

Steven A. Lowe 19.12.2008 06:50

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

Невозможно ответить на ваш вопрос как таковой, но начнем.

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

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

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

  • Каждая плитка должна наследовать от чего-то и обрабатывать сам рисунок. Кнопка кажется лучшим решением, потому что в нее уже встроена функция рисования кнопок (нажата, не нажата и т. д.).
  • Каждая плитка также должна знать своих соседей. У вас будет восемь указателей на каждого из восьми его соседей, если, конечно, установить их в ноль, если соседа нет. Когда он переходит к рисованию, он запрашивает функцию IsMine() каждого соседа и отображает счетчик.
  • Если ни один из его соседей не является шахтой, он затем рекурсивно перейдет к методу Reveal() каждого соседа.
  • Для 7-сегментного дисплея каждая цифра является отдельным классом, отвечающим за рисование. Затем я бы создал класс CountdownSegmentDigit, который наследуется от этого класса, но имеет дополнительные функции, а именно методы CountDown(), Set() и Reset(), а также событие HitZero. Тогда сам таймер дисплея представляет собой набор этих цифр, подключенных для передачи нулей влево. Затем в классе таймера есть Timer, который отсчитывает каждую секунду и отсчитывает крайнюю правую цифру.
  • Когда пользователь нажимает, см. Выше. Плитка сама обрабатывает щелчок мышью (в конце концов, это кнопка) и вызывает свой метод Reveal(). Если это мина, она вызовет событие MineExploded, которое будет прослушивать ваша основная форма.

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

Возьмите 7-сегментный бит дисплея, вы можете использовать его позже, но без обратного отсчета. Скажем, вам нужен спидометр в машине или что-то в этом роде. У вас уже есть цифры, которые вы можете соединить вместе. (Подумайте об оборудовании: стандартные 7-сегментные дисплеи, которые только светятся. Затем вы подключаете к ним контроллер, и они начинают работать.)

Фактически, если вы достаточно хорошо подумаете, вы можете обнаружить, что вам тоже нужна функциональность CountUp(). И аргумент события в HitZero, чтобы определить, было ли это счетом вверх или вниз. Но вы можете подождать, чтобы добавить эту функцию, когда она вам понадобится. Вот где сияет наследование: наследуйте для своего CountDownDigit и создайте CountUpOrDownDigit.

Размышляя о том, как я могу разработать его аппаратно, вы можете разработать каждую цифру так, чтобы она знала о своих соседях, и пересчитывала их вверх или вниз, когда это необходимо. Попросите их запомнить максимальное значение (помните, от 60 секунд до минуты, а не 100), чтобы при переходе через 0 они сбрасывались соответствующим образом. Есть мир возможностей.

Я собираюсь подождать и посмотреть, ответит ли кто-нибудь еще, но за очень короткое время, прошедшее с тех пор, как я задал этот вопрос, мне очень понравилось ваше объяснение, lc. Это вроде как подтверждает мои подозрения обо всем, что я делаю неправильно, и обо всем, что я должен делать, но не делаю.

dancavallaro 19.12.2008 07:22

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

lc. 19.12.2008 07:30

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

jamesh 19.12.2008 13:55
Ответ принят как подходящий

Вот простой (но эффективный) объектно-ориентированный дизайн, с которого можно начать:

Сначала создайте объект Game, который представляет собой чистый код Java / C#. Без пользовательского интерфейса или чего-либо еще, специфичного для платформы. Объект Game обрабатывает объект Board и объект Player. Объект Board управляет рядом объектов Tile (там, где есть мины). Объект Player отслеживает «Количество ходов», «Счет» и т. д. Вам также понадобится объект Timer для отслеживания игрового времени.

Затем создайте отдельный объект пользовательского интерфейса, который ничего не знает об объекте Game. Он полностью автономен и полностью зависит от платформы. У него есть свои собственные UIBoard, UITile, UITimer и т. д., И ему можно указать, как изменить его состояния. Объект UI отвечает за пользовательский интерфейс (вывод на экран / звук и ввод от пользователя).

И, наконец, добавьте объект Application верхнего уровня, который считывает ввод от объекта UI, сообщает Game, что делать на основе ввода, получает уведомление Game об изменениях состояния, а затем разворачивается и сообщает UI, как обновлять себя.

Это (кстати) адаптация паттерна MVP (Model, View, Presenter). И (кстати) паттерн MVP на самом деле является лишь специализацией паттерна «Посредник». И (еще один, кстати,) шаблон MVP - это в основном шаблон MVC (модель, представление, управление), где представление НЕ имеет доступа к модели. ИМХО, это большое улучшение.

Повеселись!

У вас отличная модель. Однако я думаю, это зависит от игры, которую вы пытаетесь реализовать. В случае с Minesweeper я считаю, что пользовательский интерфейс - это доска. Здесь нет искусственного интеллекта или движущихся объектов (или объектов, которые меняют состояние независимо от пользователя), поэтому нет необходимости (?) В отдельном слое игровой логики.

lc. 19.12.2008 07:45

Какие? Нет, UI никогда не бывает доской. Есть объект, который сильно меняет состояния: доска. У вас есть событие (пользователь выбирает одно поле) и ответ (некоторые поля могут быть открыты или игра может закончиться). Для меня это звучит довольно логично.

Joachim Sauer 19.12.2008 16:50

У меня есть несколько руководств, написанных на C#. Здесь обсуждается та же самая тема. Это отправная точка для игры RogueLike.

Объектно-ориентированный дизайн в C# Преобразование устаревшей игры

Объектно-ориентированный дизайн: объекты доменного типа

Объектно-ориентированный дизайн: переосмысление проблем дизайна

BROKEN LINK - Объектно-ориентированный дизайн: детские шаги при приемке

ссылки слишком старые.

TigerTV.ru 17.07.2019 23:45

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

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

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

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

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

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

Наконец, игра, которая должна работать в реальном времени, будет иметь цикл (или циклы), который будет постоянно преобразовывать состояние игры. Он будет использовать UI Controller для перерисовки того, что он обновил. Команды вставляют или изменяют информацию в модели. В следующий раз, когда появится цикл, будет использована новая информация. Например, изменение вектора движущегося по воздуху объекта.

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

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