Об'єкти нульової поведінки в ООП - моя дилема дизайну


94

Основна ідея OOP полягає в тому, що дані та поведінка (за цими даними) невіддільні і вони поєднуються з ідеєю об'єкта класу. Об'єкт має дані та методи, які працюють з цим (та іншими даними). Очевидно, що за принципами OOP об'єкти, які є лише даними (як C структури), вважаються антидіаграмою.

Все йде нормально.

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

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

Припустимо, у вас є загальний API карткової гри. У вас клас Card. Тепер цей Cardклас повинен визначити видимість для гравців.

Один із способів - це бути boolean isVisible(Player p)на Cardуроці.

Інше - мати boolean isVisible(Card c)на Playerуроці.

Мені не подобається перший підхід, оскільки він дає знання про Playerклас вищого рівня для класу нижчого рівня Card.

Натомість я вибрав третій варіант, де у нас є Viewportклас, який, з огляду на a Playerі список карток, визначає, які картки будуть видимими.

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

Це однозначно проти головної ідеї ООП.

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

PS Ось ще один приклад:

Припустимо, у вас клас, DocumentIdякий є незмінним, має лише одного BigDecimal idчлена та отримувача для цього члена. Тепер потрібно десь мати метод, який дав би DocumentIdповернення Documentцього ідентифікатора з бази даних.

Чи ти:

  • Додайте Document getDocument(SqlSession)метод до DocumentIdкласу, раптом вводячи знання про вашу наполегливість ( "we're using a database and this query is used to retrieve document by id"), API, що використовується для доступу до БД тощо. Також цей клас тепер вимагає збереження файлу JAR просто для компіляції.
  • Додайте якийсь інший клас із методом Document getDocument(DocumentId id), залишаючи DocumentIdклас мертвим, немає поведінки, клас схожий на структуру.

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

31
"Це однозначно проти головної ідеї ООП" - ні, це не так, але загальна помилка.
Doc Brown

5
Я думаю, проблема полягає в тому, що в минулому існували різні школи для "орієнтації на об'єкти" - так, як спочатку їх розуміли такі люди, як Алан Кей (див. Geekswithblogs.net/theArchitectsNapkin/archive/2013/09/08/ … ), І про те, як викладають у контексті OAA / OOD люди з Rational ( en.wikipedia.org/wiki/Object-oriented_analysis_and_design ).
Док Браун

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

5
Хто каже, що заняття без поведінки - це анти-модель?
Джеймс Андерсон

Відповіді:


42

Те, що ви описуєте, відоме як анемічна модель домену . Як і у багатьох принципах дизайну OOP (наприклад, закон Demeter тощо), не варто нахилятися назад, аби задовольнити правило.

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

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

Але чи справді це робота того, Cardщоб знати, на що Playerце видно?

І навіщо реалізовувати Card.isVisibleTo(Player p), а ні Player.isVisibleTo(Card c)? Або навпаки?

Так, ви можете спробувати придумати якесь правило для цього, як і ви - як би Playerбути на більш високому рівні, ніж Card(?) - але це не так просто здогадуватися, і мені доведеться шукати в більш ніж одному місці знайти спосіб.

Згодом це може призвести до гнилої конструкції компромісу реалізації isVisibleToна обох Card і Playerкласі, який я вважаю , це ні-ні. Чому так? Тому що я вже уявляю собі ганебний день, коли player1.isVisibleTo(card1)повернеться інша цінність, ніж card1.isVisibleTo(player1).я думаю, - це суб'єктивне - це повинно бути неможливим дизайном .

Взаємна видимість карт та гравців повинна краще керуватися якимось контекстним об'єктом - будь то Viewport, Dealабо Game.

Це не дорівнює глобальним функціям. Адже може бути багато одночасних ігор. Зауважте, що одну і ту ж карту можна використовувати одночасно на багатьох таблицях. Чи повинні ми створити багато Cardпримірників для кожного туза лопати?

Я міг би ще реалізувати isVisibleToна Card, але передати об'єкт контексту до нього і зробити Cardделегат запит. Програмуйте на інтерфейс, щоб уникнути високої зв'язку.

Що стосується вашого другого прикладу - якщо ідентифікатор документа складається лише з a BigDecimal, навіщо взагалі створювати клас обгортки для нього?

Я б сказав, що все, що вам потрібно, - це DocumentRepository.getDocument(BigDecimal documentID);

До речі, поки відсутній у Java, structв C # є.

Побачити

для довідки. Це дуже об'єктно-орієнтована мова, але ніхто не робить із цього великої справи.


1
Лише примітка про структури в C #: Вони не є типовими структурами, як ви їх знаєте в C. Насправді вони також підтримують OOP з успадкуванням, інкапсуляцією та поліморфізмом. Окрім деяких особливостей, головна відмінність полягає у тому, як час виконання обробляє випадки, коли вони передаються іншим об'єктам: структури є типовими типами, а класи - еталонними типами!
Ашратт

3
@Aschratt: Структури не підтримують успадкування. Структури можуть реалізовувати інтерфейси, але структури, що реалізують інтерфейси, поводяться інакше, ніж об'єкти класу, які так само роблять. Незважаючи на те, що можна змусити структури вести себе якось як об’єкти, найкращий варіант використання для структур, коли потрібно щось, що поводиться як структура С, а речі, які інкапсулюють, - це або примітиви, або непорушні типи класів.
supercat

1
+1, чому "це не дорівнює глобальним функціям". Цього мало займалися інші. (Хоча, якщо у вас є кілька колод, глобальна функція все одно поверне різні значення для окремих екземплярів однієї карти).
alexis

@supercat Це гідне окремого питання або сеансу чату, але мене в даний час не цікавить жодне :-( Ви кажете (в C #) "структури, що реалізують інтерфейси, поводяться інакше, ніж об'єкти класу, які так само". слід враховувати й інші поведінкові відмінності, але AFAIK в наступному коді Interface iObj = (Interface)obj;на поведінку iObjне впливає structабо classстатус obj(за винятком того, що це буде коробчаста копія при цьому призначенні, якщо воно є struct)
Марк Херд

150

Основна ідея OOP полягає в тому, що дані та поведінка (за цими даними) невіддільні і вони поєднуються з ідеєю об'єкта класу.

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

Припустимо, у вас є загальний API карткової гри. У вас є Картка класу. Тепер цей клас картки повинен визначати видимість для гравців.

ДОБРИХ НЕБУТЬ Коли ви граєте в Bridge, ви запитуєте сім сердець, коли настав час змінити руку манекена з відомого тільки секрету на манекен, щоб він був відомий усім? Звичайно, ні. Це зовсім не турбота про карту.

Один із способів - мати булевий isVisible (Player p) у класі Card. Інше - мати булевий isVisible (Картка c) у класі Player.

Обидва жахливі; не робіть жодного з них. Ні гравець, ні карта не відповідають за виконання правил Бридж!

Натомість я обрав третій варіант, де у нас клас Viewport, який, враховуючи програвач Player та перелік карт, визначає, які карти будуть видимі.

Я ніколи раніше не грав у карти із «вікном перегляду», тому не маю уявлення, що цей клас повинен містити в капсулі. Я вже грав в карти з парою колод карт, деякі гравці, стіл, і копію Хойл. Яку з цих речей представляє Viewport?

Однак такий підхід позбавляє класів карт та гравців можливих функцій членів.

Добре!

Після того, як ви зробите це для інших речей, ніж видимість карт, вам залишаються класи Card and Player, які містять суто дані, оскільки вся функціональність реалізована в інших класах, які в основному є класами без даних, просто методами, як Viewport вище. Це однозначно проти головної ідеї ООП.

Немає; основна ідея ООП полягає в тому, щоб об'єкти заглиблювали свої проблеми . У вашій системі карта мало хвилюється. Ні один гравець. Це тому, що ви точно моделюєте світ . У реальному світі властивості карт, які стосуються гри, надзвичайно прості. Ми могли замінити малюнки на картках цифрами від 1 до 52, не змінюючи особливості гри. Ми могли б замінити чотирьох людей манекенами з написом Північ, Південь, Схід та Захід, не змінюючи особливості гри. Гравці та картки - найпростіші речі у світі карткових ігор. Правила - це те, що є складним, тому клас, який представляє правила, є там, де має бути ускладнення.

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

Ось як я спроектував вашу систему.

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

Далі, яка природа гравця? Гравець споживає послідовність видимих дій і виробляє в дію .

Об'єкт правил - це те, що координує все це. Правила створюють послідовність видимих ​​дій та інформують гравців:

  • Гравець один, десять сердець вам передав гравець три.
  • Гравець два, картка була передана гравцеві одна гравцеві третій.

А потім просить гравця про дію.

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

І так далі.

Відокремте свої механізми від своєї політики . Політики гри повинні міститись в об'єкті політики , а не в картках . Картки - це лише механізм.


41
@gnat: Суперечлива думка Алана Кей: "Насправді я склав термін" об'єктно-орієнтований ", і я можу вам сказати, що я не мав на увазі C ++." Є мови OO, які не мають класів; JavaScript приходить в голову.
Ерік Ліпперт

19
@gnat: Я погодився б, що JS, як це є сьогодні, не є чудовим прикладом мови ООП, але це показує, що можна досить легко побудувати мову ОО без занять. Я погоджуюсь, що основна одиниця ОО-ності в Ейфелеві та С ++ обох - це клас; де я не згоден - це поняття, що заняття є умовою загальної якості OO. Неодмінна ГО є об'єктами , які інкапсулюють поведінку і спілкуються один з одним з допомогою чітко визначеного загального інтерфейсу.
Ерік Ліпперт

16
Я погоджуюсь w / @EricLippert, класи не є основоположними для ОО, і не є спадщиною, що б не говорило мейнстрім. Однак, інкапсуляція даних, поведінки та відповідальності досягається. Існують мови, засновані на прототипі за межами Javascript, які є OO, але без класу. Особливо помилково зосереджуватися на спадкуванні над цими поняттями. Однак, заняття є дуже корисним засобом організації інкапсуляції поведінки. Те, що ви можете ставитися до класів як до об'єктів (і в мовах прототипів, навпаки), робить лінію розмитою.
Шверн

6
Розглянемо це так: яку поведінку фактична карта проявляє самостійно? Я думаю, що відповідь - "ні". Інші речі діють на картці. Сама карта, в реальному світі, буквально є лише інформацією (4 клуби), без внутрішньої поведінки будь-якого виду. Використання цієї інформації (також "картки") на 100% залежить від чогось / когось іншого, як "правила" та "гравці". Одні і ті ж картки можуть використовуватися для нескінченного (ну, може, не зовсім) різноманітності різних ігор, будь-якою кількістю різних гравців. Картка - це лише картка, і все, що вона має, - це властивості.
Крейг

5
@Montagist: Дозвольте мені трохи прояснити речі. Поміркуйте C. Я думаю, ви погодилися б, що в C немає класів. Однак ви можете сказати, що структури - це "класи", ви можете створювати поля типу вказівника функції, ви можете створювати vtables, ви можете робити методи, звані "конструкторами", які встановлюють vtables так, що деякі структури "успадковуються" один від одного, і так далі. Можна наслідувати наслідування на основі класу в C. І можна наслідувати його в JS. Але робити це означає будувати щось над мовою, якої ще немає.
Ерік Ліпперт

29

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

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

Хоча OOP має деякі явні переваги (особливо перед звичайним процедурним програмуванням), наївно прагнути до «чистого» OOP. Деякі проблеми не підходять до об'єктно-орієнтованого підходу, а інші парадигми вирішуються легше. Зустрічаючи таку проблему, не наполягайте на неповноцінному підході.

  • Розглянемо обчислення послідовності Фібоначчі об'єктно-орієнтованим чином . Я не можу придумати розумного способу зробити це; просте структуроване програмування пропонує найкраще рішення цієї проблеми.

  • Ваше isVisibleвідношення належить до обох класів, або до жодного, або власне: до контексту . Записи без поведінки характерні для функціонального або процедурного підходу до програмування, який, здається, найкраще відповідає вашій проблемі. У цьому немає нічого поганого

    static boolean isVisible(Card c, Player p);
    

    і немає нічого поганого в Cardтому, щоб не було методів, rankа також suitаксесуарів.


11
@UMad так, це точно моя думка, і в цьому немає нічого поганого. Використовуйте правильну парадигму <del> мови </del> для роботи. (До речі, більшість мов, крім Smalltalk, не є чисто об'єктно-орієнтованою. Наприклад, Java, C # і C ++ підтримують імперативне, структуроване, процедурне, модульне, функціональне та об'єктно-орієнтоване програмування. Усі ці парадигми, що не належать до OO, доступні не просто : щоб ви могли ними скористатися)
amon

1
Існує розумний спосіб OO зробити Фібоначчі, викликати fibonacciметод за екземпляром integer. Я хочу підкреслити вашу думку, що ОО - це про інкапсуляцію, навіть у, здавалося б, невеликих місцях. Нехай ціле число зрозуміє, як виконати роботу. Пізніше ви можете покращити реалізацію, додати кешування для підвищення продуктивності. На відміну від функцій, методи слідують за даними, тому всі абоненти отримують перевагу від покращеної реалізації. Можливо, довільні точні цілі числа будуть додані пізніше, вони можуть бути прозоро оброблені як звичайні цілі числа і можуть мати свій власний fibonacciметод продуктивності .
Шверн

2
@Schwern, якщо що-небудь Fibonacciє підкласом абстрактного класу Sequence, послідовність використовується будь-яким набором номерів і відповідає за збереження насіння, стан, кешування та ітератор.
Джордж Рейт

2
Я не очікував, що «чистий OOP Fibach» буде настільки ефективним при обробці духів . Будь ласка, припиніть будь-яке кругове обговорення цього питання в цих коментарях, хоча воно мало певне розважальне значення. Тепер давайте всі зробимо щось конструктивне для зміни!
амон

3
було б нерозумно скласти метод методу цілих чисел, просто так можна сказати, що це OOP. Це функція і до неї слід ставитися як до функції.
іммібіс

19

Основна ідея OOP полягає в тому, що дані та поведінка (за цими даними) невіддільні і вони поєднуються з ідеєю об'єкта класу. Об'єкт має дані та методи, які працюють з цим (та іншими даними). Очевидно, що за принципами OOP об'єкти, які є лише даними (як C структури), вважаються антидіаграмою. (...) Це однозначно проти головної ідеї ООП.

Це складне питання, оскільки воно базується на досить багатьох несправних приміщеннях:

  1. Ідея, що OOP - єдиний вірний спосіб написання коду.
  2. Ідея, що ООП - це чітко визначена концепція. Це стало таким казковим словом, що важко знайти двох людей, які могли б домовитися про те, що йдеться про ООП.
  3. Ідея, що OOP полягає у згуртуванні даних та поведінки.
  4. Думка про те, що все є / має бути абстракцією.

Я не зачіпаю №1-3 дуже багато, тому що кожен може породити власну відповідь, і це запрошує багато дискусій на основі думок. Але я вважаю, що ідея "OOP - це про з'єднання даних та поведінки" є особливо тривожною. Це не тільки призводить до №4, але й веде до думки, що все має бути методом.

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

OOP - це використання об'єктів. Об'єкти - це лише один із способів досягнення абстракції . Абстракція - це засіб уникнути зайвого зв’язку у вашому коді, а не самоціль. Якщо ваше уявлення про карту визначене виключно за значенням набору та рангу, цілком реально реалізувати її як простий кортеж або запис. Немає несуттєвих деталей, від яких будь-яка інша частина коду може формувати залежність. Іноді просто немає чого приховувати.

Ви б не isVisibleвикористовували метод такого Cardтипу, оскільки видимість, швидше за все, не є суттєвою для вашого уявлення про карту (якщо тільки у вас немає спеціальних карток, які можуть стати напівпрозорими або непрозорими ...). Чи повинен це бути метод такого Playerтипу? Ну, мабуть, це теж не визначальна якість гравців. Чи повинна вона бути частиною якогось Viewportтипу? Знову ж таки, це залежить від того, яким ви визначаєте вікно перегляду та чи не є поняття перевірки видимості карт невід'ємним для визначення вікна перегляду.

Це дуже можливо, isVisibleпросто повинна бути вільна функція.


1
+1 для здорового глузду замість безглуздих дронів.
праворуч

Із прочитаних рядків нарис, який ви пов’язали, виглядає так, як одне суцільне прочитання, яке я не мав за деякий час.
Артур Гавлічек

@ArthurHavlicek Це важче дотримуватися, якщо ти не розумієш мови, які використовуються у зразку коду, але я вважав це досить освітленим.
Doval

9

Очевидно, що за принципами OOP об'єкти, які є лише даними (як C структури), вважаються антидіаграмою.

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

У той час як ваш рівень даних може зіпсувати повний Playerклас під час читання з Playersтаблиці, натомість він може бути просто загальною бібліотекою даних, яка повертає POD з полями з таблиці, яку він передає в іншу область програми, яка перетворює гравця ПОД у ваш конкретний Playerклас.

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


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

2
Точно, картки та документи - це лише контейнери інформації навіть у реальному світі, і будь-який "зразок", який не може впоратися з цим, потрібно ігнорувати.
JeffO

1
Plain-Old-Data objects are a perfectly valid pattern Я не сказав, що вони не були, я кажу, що це неправильно, коли вони заповнюють всю нижню половину програми.
RokL

8

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

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

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

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


Відповідь Ліпперта вище - кращий приклад цієї концепції.
RibaldEddie

5

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

Як це стосується Card- Playerпроблема: Створення ViewPortабстракції має сенс, якщо ви думаєте Cardі Playerяк про дві незалежні бібліотеки (що означало б, що Playerіноді використовується без Card). Однак я схильний думати про Playerзатримку Cardsі повинен надати доступ Collection<Card> getVisibleCards ()до них. Обидва ці рішення ( ViewPortі моє) є кращими, ніж надання isVisibleв якості методу Cardабо Player, з точки зору створення зрозумілих кодових зв’язків.

Позакласне рішення - це набагато, набагато краще для DocumentId. Мало мотивації зробити (в основному цілим числом) залежність від складної бібліотеки баз даних.


Мені подобається твій блог.
RokL

3

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

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

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

Я мав підштовхнути досвідчених, щоб подивитися на проблему. Я думаю, що є гарне питання, на яке підштовхнув U Mad. Я розумію, що ви б підштовхнули правила і відповідну логіку до власного об'єкта; але, як я розумію, питання є

  1. Чи добре мати прості конструкції власників даних (класи / структури; мені не байдуже, що вони моделюють щодо цього питання), які насправді не пропонують великої функціональності?
  2. Якщо так, то який найкращий чи бажаний спосіб моделювати їх?
  3. Якщо ні, як ми включаємо ці лічильники даних у вищі класи API (включаючи поведінку)

Мій погляд:

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


3
Проблема полягає в тому, що питання (і ваша відповідь) стосуються того, як робити справи "загалом". Справа в тому, що ми ніколи не робимо речі "взагалі". Ми завжди робимо конкретні речі. Необхідно вивчити наші конкретні речі та порівняти їх з нашими вимогами, щоб визначити, чи є наші конкретні речі правильними для ситуації.
Джон Сондерс

@JohnSaunders Я сприймаю вашу мудрість тут і погоджуюсь певною мірою, але перед тим, як вирішити проблему, потрібен просто концептуальний підхід. Адже питання тут не таке відкрите, як здається. Я думаю, що це правильне питання щодо ЛЗ, з яким стикаються будь-які дизайнери ОО в початкових звичках OOP. Що ти приймаєш? Якщо конкремент допомагає, ми можемо обговорити побудову прикладу на ваш вибір.
Харша

Я вже поза школою більше 35 років. У реальному світі я знаходжу дуже мало значення в «концептуальних підходах». Я вважаю досвід кращим вчителем, ніж Мейєрс у цьому випадку.
Джон Сондерс

Я не дуже розумію клас для даних проти класу для розрізнення поведінки. Якщо ви абстрагуєте свої об'єкти належним чином, різниці немає. Уявіть собі, Pointз getX()функцією. Ви можете собі уявити, що він отримує один з своїх атрибутів, але він також може читати його з диска чи Інтернету. Отримання та налаштування - це поведінка, а заняття, які роблять саме це, є цілком прекрасним. Бази даних отримують і встановлюють дані fwiw
Артур Гавлічек,

@ArthurHavlicek: Знання того , що не буде робити клас , так само корисне, як і знання того, що він буде робити. Якщо у договорі щось вказано, що він буде вести себе не що інше, як власник змінних змінних даних, або як не що інше, як власник змінних даних, що не можна поділити.
supercat

2

Одне загальне джерело плутанини в ООП випливає з того, що багато об'єктів містять два аспекти стану: те, про що вони знають, і те, що про них відомо. Обговорення стану об'єктів часто ігнорують останній аспект, оскільки в рамках, де посилання на об'єкти є розбірливими, немає загального способу визначення того, що може знати про будь-який об'єкт, посилання якого коли-небудь піддавалися зовнішньому світу.

Я б припустив, що, мабуть, було б корисно мати CardEntityоб’єкт, який інкапсулює ці аспекти карти в окремі компоненти. Один компонент стосується позначок на картці (наприклад, "Diamond King" або "Lava Blast; гравці мають шанс ухилитися від AC-3 або інакше отримати шкоду 2D6"). Можливо, це стосується унікального аспекту стану, такого як положення (наприклад, це на палубі, або в руці Джо, або на столі перед Ларрі). Третій може стосуватися того, хто може його бачити (можливо, ніхто, можливо один гравець, чи, можливо, багато гравців). Щоб все не синхронізоване, місця, де може бути карта, не будуть інкапсульовані як прості поля, а скоріше CardSpaceоб'єкти; щоб перемістити карту в пробіл, можна дати їй посилання на належнеCardSpaceоб’єкт; Потім він видалить себе зі старого простору і поставить себе у новий простір).

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


0

Однак такий підхід позбавляє класів карт та гравців можливих функцій членів.

І як це погано / нераціонально?

Щоб використати аналогію з прикладом карт, розгляньте, a Car, a Driverі вам потрібно визначити, чи Driverможна керувати Car.

Гаразд, тож ви вирішили, що не хочете, щоб ваш чоловік Carзнав, чи Driverмає правильний ключ автомобіля чи ні, і з незрозумілої причини ви також вирішили, що не хочете, щоб ваш клас Driverзнав Car(ви не повністю плоть це також у вашому оригінальному запитанні). Отже, у вас є посередницький клас, що відповідає принципам Utilsкласу, який містить метод із правилами бізнесу , щоб повернути booleanзначення для вищезазначеного питання.

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

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