Java:как аккуратно обработать большое количество полей и их инкапсуляцию?

StackOverflow https://stackoverflow.com/questions/768783

  •  12-09-2019
  •  | 
  •  

Вопрос

Допустим, мне поручено написать какую-то RPG.Это означает, что, например, я захочу отследить Character GameCharacter и связанные с этим характеристики, такие как интеллект, бонусы за урон или хитпоинты.

Я положительно боюсь, что к концу проекта я могу столкнуться с обработкой очень большого количества полей - и для каждого я должен был бы убедиться, что они соответствуют очень похожему набору ограничений и поведений (например, я хочу, чтобы они были ограничены между минимальным и максимальным значениями;Я хочу иметь возможность различать "базовую стоимость" и "временный бонус".;Я хочу иметь возможность увеличивать и уменьшать как то, так и другое, не проходя через сеттеры и геттеры).Внезапно для каждого поля мне понадобился бы один (два?) геттера и четыре сеттера и, возможно, еще пара ресеттеров!Даже для 10 полей это означает множество одинаковых методов, черт возьми.

Для наглядности я начал инкапсулировать логику работы с этими статистическими данными в Field классы, чтобы я мог писать такой код, как intelligence.applyBonus(10) или hitpoints.get() (который заботится о том, чтобы возвращаемое значение находилось в диапазоне) и т.д.Я даже потратил столько времени на создание классов для группировки этих полей вместе, но сейчас не в этом дело.

Теперь я столкнулся с этой проблемой во время "подключения". Field в GameCharacter:в большинстве учебников Java говорится, что каждый класс должен иметь закрытые поля с общедоступными получателями и установщиками.Теоретически это звучит неплохо, и я уже построил целый класс вокруг int;однако эта идея звучит не так убедительно, когда вы обнаруживаете, что звоните получателю, чтобы получить...добытчик:

thisCharacter.getIntelligence().get() //eeek

Я бы предпочел получить доступ к полю напрямую.Может быть, это мой Python / VB [1] "background", но для меня он чище, понятнее и понятнее:

thisCharacter.intelligence.get()

(Теоретическая) проблема с общедоступными полями заключается в том, что я отказываюсь от всякого контроля над ними;например, в какой-то другой точке кодовой базы, к несчастью, может произойти следующее:

thisCharacter.intelligence = somethingThatReallyIsNull;

Звучит как незаметная ошибка...но...Я имею в виду, стоит ли мне действительно беспокоиться об этом?Я, например, никогда не планирую назначать Field непосредственно [2] я задокументировал в Javadoc, что это не то, что следует делать, но все же я здесь новичок, поэтому я немного растерян.

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


[1] Да, я знаю.Я пытался забыть.Но совсем недавно мы также познакомились с C # и man, не правда ли, приятными свойствами.Ну что ж.

[2] за исключением конструкторов!И геттер не спасет меня от неисправного конструктора.

Это было полезно?

Решение

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

Например, иметь сопоставление атрибутов от ключей к значениям.

Предоставляйте публичные запросы на получение и настройку атрибутов, но не позволяйте всем использовать их (или убедитесь, что они этого не делают).Вместо этого создайте классы для представления каждого интересующего вас атрибута, и этот класс предоставит все функции для манипулирования этим атрибутом.Например, если у вас есть Strength, у вас может быть класс "StrengthManipulation", который инициализируется для определенного объекта Player, а затем предоставляет геттеры, сеттеры (все с соответствующей проверкой и исключениями) и, возможно, такие вещи, как вычисление силы с помощью бонусов и т.д.

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

Что касается прямого доступа к полям, то это плохая идея.Когда вы обращаетесь к полю в VB (по крайней мере, в старом VBs), вы обычно вызываете средство получения и установки свойств, и VB просто скрывает для вас вызов ().Мое мнение таково, что вы должны адаптироваться к условностям языка, который вы используете.В C, C ++, Java и тому подобном у вас есть поля и у вас есть методы.При вызове метода всегда должна быть функция (), чтобы было ясно, что это вызов и могут произойти другие вещи (например, вы можете получить исключение).В любом случае, одним из преимуществ Java является ее более точный синтаксис и стиль.

Перевод с VB на Java или C ++ подобен отправке текстовых сообщений для написания научных статей в аспирантуре.

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

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

Звучит так, будто вы мыслите в терминах if(player.dexterity > monster.dexterity) attacker = player.Тебе нужно больше думать о том, как if(player.quickerThan(monster)) monster.suffersAttackFrom(player.getCurrentWeapon()).Не связывайтесь с голой статистикой, выразите свое реальное намерение, а затем разрабатывайте свои классы в соответствии с тем, что они должны делать.Статистика в любом случае - это отговорка;что вас действительно волнует, так это то, может или не может игрок совершить какое-то действие, или его способности в сравнении с каким-то эталоном.Подумайте в терминах "достаточно ли силен персонаж игрока". (player.canOpen(trapDoor)) вместо "обладает ли персонаж силой не менее 50".

У Стива Йегге был очень интересный (хотя и длинный) пост в блоге, в котором обсуждались эти вопросы: Универсальный Шаблон дизайна.

Мне кажется, что у "этого персонажа" вполне может быть объект "intelligence" для работы с разведданными за кулисами, но я сомневаюсь, что это вообще должно быть публично.Вы должны просто предоставить thisCharacter.applyInt и thisCharacter.getInt вместо объекта, который имеет с ним дело.Не выставляйте свою реализацию напоказ подобным образом.

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

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

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

Похоже, ваша основная жалоба связана не столько с абстракцией методов setter / getter, сколько с синтаксисом языка для их использования.Т.е. вы бы предпочли что-то вроде свойств в стиле C #.

Если это так, то язык Java может предложить вам относительно мало.Прямой доступ к полю - это нормально, пока вам не понадобится переключиться на средство получения или установки, а затем вам нужно будет выполнить некоторый рефакторинг (возможно, ок, если вы контролируете всю кодовую базу).

Конечно, если платформа Java является обязательным требованием, а язык - нет, тогда есть другие альтернативы.Например, Scala обладает очень приятным синтаксисом свойств, наряду с множеством других функций, которые могли бы быть полезны для такого проекта.И что лучше всего, он работает на JVM, так что вы по-прежнему получаете ту же переносимость, которую получили бы, написав его на языке Java.:)

Похоже, что у вас здесь есть один слой составной модели.

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

Поля должны быть окончательными, поэтому, даже если вы сделаете их общедоступными, вы не сможете случайно назначить null для них.

Префикс "get" присутствует для всех геттеров, так что, вероятно, это скорее начальный вид, чем новая проблема как таковая.

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

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

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

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

Вместо того, чтобы раскрывать реализацию GameCharacter с помощью setIntelligence, почему бы не предоставить GameCharacter интерфейс, который лучше отражает его роль в игровой системе?

Например, вместо:

// pseudo-encapsulation anti-pattern
public class GameCharacter
{
  private Intelligence intelligence;

  public Intelligence getIntelligence()
  {
    return intelligence
  }

  public void setIntelligence(Intelligence intelligence)
  {
    this.intelligence = intelligence;
  }
}

почему бы не попробовать это?:

// better encapsulation
public class GameCharacter
{
  public void grabObject(GameObject object)
  {
    // TODO update intelligence, etc.
  }

  public int getIntelligence()
  {
    // TODO
  }
}

или даже лучше:

// still better
public interface GameCharacter
{
  public void grabObject(GameObject object); // might update intelligence
  public int getIntelligence();
}

public class Ogre implements GameCharacter
{
  // TODO: never increases intelligence after grabbing objects
}

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

Обратите внимание, что GameCharacter теперь отвечает за обработку собственного обновления интеллекта (проверка диапазона и т.д.), Которое может происходить, например, при захвате игровых объектов.Сеттер (и сложности, которые вы заметили при его наличии) исчезли.Возможно, вы сможете вообще отказаться от метода getIntelligence, в зависимости от ситуации.Аллен Голуб воплощает эту идею в жизнь. логический вывод, но, но такой подход, похоже, не очень распространен.

В дополнение к ответу Uri (который я полностью поддерживаю), я хотел бы предложить вам рассмотреть возможность определения карты атрибутов в data.Это сделает вашу программу ЧРЕЗВЫЧАЙНО гибкой и исключит большое количество кода, о котором вы даже не подозреваете, который вам не нужен.

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

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

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

Пройдя этот путь, вы, вероятно, окажетесь с довольно серьезным текстовым файлом для определения атрибутов и навыков, но добавить новый навык будет так же просто, как:

Skill: Weaving
  DBName: BasketWeavingSkill
  DisplayLocation: 102, 20  #using coordinates probably isn't the best idea.
  Requires: Int=8
  Requires: Dex=12
  MaxLevel=50

В какой-то момент добавление подобного навыка не потребовало бы никаких изменений кода вообще, все это можно было бы довольно легко выполнить в data, и ВСЕ данные хранятся в одном объекте skill, прикрепленном к классу.Вы могли бы, конечно, определить действия таким же образом:

Action: Weave Basket
  text: You attempt to weave a basket from straw
  materials: Straw
  case Weaving < 1
    test: You don't have the skill!
  case Weaving < 10
    text: You make a lame basket
    subtract 10 straw
    create basket value 8
    improve skill weaving 1%
  case Weaving < 40
    text: You make a decent basket
    subtract 10 straw
    create basket value 30
    improve skill weaving 0.1%
  case Weaving < 50
    text: You make an awesome basket!
    subtract 10 straw
    create basket value 100
    improve skill weaving 0.01%
  case Weaving = 50
    text: OMG, you made the basket of the gods!
    subtract 10 straw
    create basket value 1000

Хотя этот пример довольно сложный, вы должны быть в состоянии представить, как это было бы сделано абсолютно без кода.Представьте, как трудно было бы сделать что-либо подобное без кода, если бы ваши "атрибуты / навыки" на самом деле были переменными-членами.

Рассмотрите возможность использования фреймворка "моделирования", такого как EMF от Eclipse.Это включает в себя определение ваших объектов с помощью чего-то вроде Eclipse EMF editor, или в обычном XML, или в Rational Rose.Это не так сложно, как кажется.Вы определяете атрибуты, ограничения и т.д.Затем фреймворк генерирует код для вас.Он добавляет теги @generated к добавленным частям, таким как методы получения и установки.Вы можете настроить код, а затем отредактировать его вручную или с помощью какого-либо графического интерфейса, а также повторно создать файлы java.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top