Насколько конкретным должен быть шаблон единой ответственности для классов?

14

Например, предположим, что у вас есть консольная игровая программа, которая имеет все виды методов ввода / вывода в консоль и из консоли. Будет ли это быть умным , чтобы держать их все в одном inputOutputклассе или разбить их на более конкретных классы , как startMenuIO, inGameIO, playerIO, gameBoardIOи т.д. , так что каждый класс имеет около 1-5 методов?

И в то же время, если лучше разбить их на части, было бы разумно поместить их в IOпространство имен, сделав их более многословными, например: IO.inGameи т. Д.?

Shinzou
источник
Связанный: я никогда не видел хорошей причины объединить ввод и вывод. И когда я намеренно разделяю их, мой код оказывается намного чище.
Mooing Duck
1
В любом случае, на каком языке вы называете классы в lowerCamelCase?
user253751

Ответы:

7

Обновление (резюме)

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

  • Пространства имен хороши, используйте их всякий раз, когда это имеет смысл
  • Использование inGameIOи playerIOклассы, скорее всего , представляют собой нарушение SRP. Это, вероятно, означает, что вы связываете способ обработки ввода-вывода с логикой приложения.
  • Есть пара общих классов ввода-вывода, которые используются (или иногда совместно используются) классами обработчиков. Эти классы-обработчики затем переведут необработанный ввод в формат, понятный вашей логике приложения.
  • То же самое касается вывода: это может быть сделано довольно общими классами, но передают игровое состояние через объект обработчика / картографа, который переводит внутреннее игровое состояние в то, что могут обрабатывать универсальные классы ввода-вывода.

Я думаю, что вы смотрите на это неправильно. Вы отделяете IO в зависимости от компонентов приложения, тогда как для меня более разумно иметь отдельные классы IO, основанные на источнике и «типе» IO.

Имея несколько базовых / универсальных KeyboardIOклассов MouseIOдля начала, а затем основываясь на том, когда и где они вам нужны, есть подклассы, которые по-разному обрабатывают указанный IO.
Например, ввод текста - это то, что вы, вероятно, хотите обрабатывать иначе, чем внутриигровые элементы управления. Вы обнаружите, что хотите отображать определенные ключи по-разному в зависимости от каждого варианта использования, но это сопоставление не является частью самого IO, это то, как вы обрабатываете IO.

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

Затем я вставил бы эти объекты в объект-обработчик, который либо отобразил бы необработанный ввод-вывод на что-то, с чем могла бы работать логика моего приложения (например: пользователь нажимает «w» , обработчик отображает это на MOVE_FORWARD).

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

[ IO.Keyboard.InGame ] // generic, if SoC and SRP are strongly adhered to, changing this component should be fairly easy to do
   ||
   ==> [ Controls.Keyboard.InGameMapper ]

[ Game.Engine ] <- Controls.Keyboard.InGameMapper
                <- IO.Screen
                <- ... all sorts of stuff here
    InGameMapper.move() //returns MOVE_FORWARD or something
      ||
      ==> 1. Game.updateStuff();//do all the things you need to do to move the character in the given direction
          2. Game.Screen.SetState(GameState); //translate the game state (inverse handler)
          3. IO.Screen.draw();//generate actual output

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

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

Обработчик переводит это во внутреннее представление для остальной части приложения, чтобы понять эту информацию.

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

Эти объекты затем возвращают свое состояние обратно, и эти данные передаются Game.Screen, что по сути является обратным обработчиком ввода-вывода. Он отображает внутреннее представление на то, что IO.Screenкомпонент может использовать для генерации фактического вывода.

Элиас Ван Отегем
источник
Поскольку это консольное приложение, в нем нет мыши, и печать сообщений или доски объявлений тесно связана с вводом. В вашем примере, являются IOи gameпространства имен или классов с подклассами?
Синдзоу
@kuhaku: это пространства имен. Суть того, что я говорю, заключается в том, что, если вы решите создавать подклассы на основе того, в какой части приложения вы находитесь, вы эффективно тесно связываете базовую функциональность ввода-вывода с логикой приложения. В итоге вы получите классы, отвечающие за ввод-вывод в функции приложения . Для меня это звучит как нарушение ПСП. Что касается имен против классов с пространством имен: я склонен отдавать предпочтение пространствам имен 95% времени
Элиас Ван Оотегем
Я обновил свой ответ, чтобы подвести итог моего ответа
Элиас Ван Оотегем
Да, это на самом деле еще одна проблема, которую я имею (соединение ввода-вывода с логикой), так что вы действительно рекомендуете отделить вход от выхода?
Синдзоу
@kuhaku: Это действительно зависит от того, что вы делаете, и насколько сложен материал ввода / вывода. Если обработчики (перевод состояния игры против необработанного ввода / вывода) слишком сильно отличаются, то вы, вероятно, захотите разделить входной и выходной классы, если нет, то подходит один класс ввода-вывода
Элиас Ван Оотегем
23

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

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

Затем вы можете определить такие вещи, как «транспортное средство», «дорога», «колеса» и т. Д. Отдельно. Вы бы не попытались сказать:

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

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

Вон Катон
источник
Так как с определением автомобиля с помощью нескольких слов вместо многих, тогда можно определить небольшие специфические, но похожие классы?
Shinzou
2
@kuhaku: маленький это хорошо. Конкретно это хорошо. Подобное прекрасно, пока вы держите его сухим. Вы пытаетесь сделать свой дизайн легко изменяемым. Хранение мелочей и конкретных вещей поможет вам знать, где вносить изменения. Сохранение этого СУХОГО помогает избежать необходимости менять множество мест.
Вон Катон
6
Ваше второе предложение выглядит так: «Черт, учитель сказал, что это должно быть 4 страницы ...« генерирует движущую силу », это так !!»
CorsiKa
2

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

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

Zalomon
источник
0

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

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

Грег Бургардт
источник