Наскільки специфічним повинен бути шаблон єдиної відповідальності для занять?


14

Наприклад, припустимо, що у вас є консольна ігрова програма, яка має всі види методів введення / виводу на консоль і з них. Чи буде це бути розумним , щоб тримати їх все в одному inputOutputкласі або розбити їх на більш конкретних класи , як startMenuIO, inGameIO, playerIO, gameBoardIOі т.д. , так що кожен клас має близько 1-5 методів?

І на ту саму примітку, якщо краще розбити їх, чи було б розумним розмістити їх у IOпросторі імен, зробивши так, щоб називати їх трохи більш багатослівними, наприклад: IO.inGameтощо?



Пов'язане: Я ніколи не бачив вагомих причин поєднувати введення та вихід. І коли я навмисно їх відокремлюю, мій код виходить набагато чистішим.
Mooing Duck

1
Якою мовою ви називаєте класи в нижньомуCamelCase?
користувач253751

Відповіді:


7

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

Оскільки я написав досить багатослівну відповідь, ось до чого все зводиться:

  • Простори імен хороші, використовуйте їх, коли це має сенс
  • Використання inGameIOта playerIOзаняття, ймовірно, становлять порушення СРП. Це, ймовірно, означає, що ви з'єднуєте спосіб обробки IO з логікою програми.
  • Майте пару загальних класів вводу-виводу, які використовуються (або іноді поділяються) класами обробників. Ці класи обробників потім переведуть необроблений вхід у формат, з якого може мати сенс ваша логіка програми.
  • Те ж саме стосується і результату: це можна зробити досить загальними класами, але передати стан гри через об'єкт обробника / картографування, який переводить внутрішній стан гри в щось, з чим можуть входити загальні класи IO.

Я думаю, що ти дивишся на це неправильно. Ви відокремлюєте IO у функції компонентів програми, тоді як - на мене, має сенс мати окремі класи IO на основі джерела та "типу" IO.

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

Дотримуючись SRP, у мене було б кілька класів, які я можу використовувати для клавіатури IO. Залежно від ситуації я, мабуть, захочу по-різному взаємодіяти з цими класами, але їх єдиною роботою є розповісти мені, що робить користувач.

Потім я б вводив ці об'єкти в оброблювальний об'єкт, який би або картував сирий IO на те, з чим може працювати моя логіка програми (наприклад: користувач натискає "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

Зараз у нас є клас, який відповідає за IO клавіатури у своєму сирому вигляді. Ще один клас, який переводить ці дані в щось, що ігровий двигун може насправді мати сенс, ці дані потім використовуються для оновлення стану всіх компонентів, що беруть участь, і, нарешті, окремий клас подбає про вихід на екран.

Кожен клас має одну задачу: обробка вводу клавіатури виконується класом, який не знає / доглядає / повинен знати, що означає вхід, який він обробляє. Все, що він робить - це знати, як отримати вхід (буферизований, небуферований, ...).

Обробник переводить це у внутрішнє представлення для решти програми, щоб зрозуміти цю інформацію.

Ігровий движок бере дані, які були перекладені, і використовує їх для сповіщення всіх відповідних компонентів про те, що щось відбувається. Кожен з цих компонентів виконує лише одне, будь то перевірка зіткнення чи зміна анімації символів, не важливо, це залежить від кожного окремого об'єкта.

Потім ці об'єкти передають свій стан назад, і ці дані передаються Game.Screen, що по суті є оберненим обробником IO. Він відображає внутрішнє представлення на те, що IO.Screenкомпонент може використовувати для отримання фактичного виводу.


Оскільки це консольна програма, немає миші, а друк повідомлень чи дошки тісно пов'язаний із входом. У вашому прикладі, є IOі gameпростору імен або класів з підкласами?
shinzou

@kuhaku: Вони простори імен. Суть того, що я говорю, полягає в тому, що якщо ви вирішите створити підкласи на основі тієї частини програми, в якій ви перебуваєте, ви ефективно щільно з'єднуєте основний функціонал IO з логікою вашого додатка. Ви закінчите класи, які відповідають за IO у функції програми . Це, на мене, звучить як порушення СРП. Що стосується класів з іменами проти імен: я, як правило, віддаю перевагу просторам імен 95% часу
Elias Van Ootegem

Я оновив свою відповідь, щоб узагальнити свою відповідь
Elias Van Ootegem

Так, це насправді ще одна проблема, яка у мене є (з'єднання IO з логікою), тому ви насправді рекомендуєте відокремити вхід від виводу?
shinzou

@kuhaku: Це дійсно залежить від того, чим ти займаєшся, і наскільки складними є матеріали введення / виведення. Якщо обробники (переклад ігрового стану та сировини введення / виведення) відрізняються занадто сильно, то, ймовірно, ви хочете розділити класи введення та виведення, якщо ні, то один клас IO добре
Elias Van Ootegem

23

Принцип єдиної відповідальності може бути складним для розуміння. Що мені здалося корисним - це думати про це, як писати речення. Ви не намагаєтесь вбити багато ідей в одне речення. У кожному реченні слід чітко викладати одну ідею та відкладати деталі. Наприклад, якщо ви хочете визначити автомобіль, ви б сказали:

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

Тоді ви окремо визначили б такі речі, як "транспортний засіб", "дорога", "колеса" тощо. Ви б не намагалися сказати:

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

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


Так як, наприклад, визначити машину з кількома словами замість багатьох, тоді добре визначити невеликі конкретні, але подібні класи?
shinzou

2
@kuhaku: Малий - це добре. Специфічний - це добре. Подібне чудово, доки ви його не сушите. Ви намагаєтесь зробити дизайн легко змінити. Зберігання дрібних та конкретних речей допомагає вам знати, де вносити зміни. Утримуючи його СУХО, можна уникнути необхідності міняти багато місць.
Вон Катон

6
Ваше друге речення виглядає так: "Чорт, учитель сказав, що це має бути 4 сторінки ..." генерує мотив "це !!"
corsiKa

2

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

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


0

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

Щоб відповісти на ваше запитання, я мушу поставити вам запитання: чи має ваш клас лише одну причину для зміни? Якщо ні, то не бійтеся продовжувати додавати більше спеціалізованих класів, поки кожен не має лише однієї причини змінитись.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.