Нулевое поведение объектов в ООП - моя дилемма дизайна

94

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

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

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

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

Предположим, у вас есть общий API для карточных игр. У вас есть класс Card. Теперь этот Cardкласс должен определить видимость для игроков.

Один из способов иметь boolean isVisible(Player p)на Cardклассе.

Другой должен иметь boolean isVisible(Card c)в Playerклассе.

Мне особенно не нравится первый подход, поскольку он предоставляет знания о Playerклассе более высокого уровня классу более низкого уровня Card.

Вместо этого я выбрал третий вариант, где у нас есть Viewportкласс, который, учитывая 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класс мертвым, без поведения, как структурный класс.
RokL
источник
21
Некоторые из ваших предположений здесь совершенно неверны, что затруднит ответ на основной вопрос. Ваши вопросы должны быть как можно более краткими и непредвзятыми, и вы получите лучшие ответы.
фунтовые
31
«Это явно противоречит основной идее ООП» - нет, это не так, но это распространенная ошибка.
Док Браун
5
Я предполагаю, что проблема заключается в том, что в прошлом существовали разные школы для «объектной ориентации» - так, как это изначально подразумевали такие люди, как Алан Кей (см. Geekswithblogs.net/theArchitectsNapkin/archive/2013/09/08/ … ), И способ преподавания в контексте OOA / OOD был предоставлен людьми из Rational ( en.wikipedia.org/wiki/Object-oriented_analysis_and_design ).
Док Браун
21
Это очень хороший вопрос, и он хорошо опубликован - вопреки некоторым другим комментариям, я бы сказал. Он ясно показывает, насколько наивны или неполны большинство советов о том, как структурировать программу, и насколько трудно это сделать, и насколько недостижимо правильное проектирование во многих ситуациях, независимо от того, насколько стараешься сделать это правильно. И хотя очевидным ответом на конкретный вопрос являются мульти-методы, основная проблема проектирования сохраняется.
Тьяго Сильва
5
Кто говорит, что классы без поведения - это не образец?
Джеймс Андерсон

Ответы:

42

То, что вы описываете, называется моделью анемичной области . Как и во многих принципах проектирования ООП (таких как Закон Деметры и т. Д.), Не стоит отклоняться назад, просто чтобы выполнить правило.

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

Конечно, было бы запахом кода, если бы у вас был отдельный класс только для изменения свойств 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. На самом деле, они также поддерживают ООП с наследованием, инкапсуляцией и полиморфизмом. Помимо некоторых особенностей, главное отличие состоит в том, как среда выполнения обрабатывает экземпляры, когда они передаются другим объектам: структуры являются типами значений, а классы являются ссылочными типами!
Ашратт
3
@Aschratt: структуры не поддерживают наследование. Структуры могут реализовывать интерфейсы, но структуры, которые реализуют интерфейсы, ведут себя иначе, чем объекты классов, которые делают то же самое. Хотя можно заставить структуры вести себя в некоторой степени как объекты, наилучший вариант использования структур - это когда кто-то хочет что-то, что ведет себя как структура C, и вещи, которые он инкапсулирует, являются либо примитивами, либо неизменяемыми типами классов.
суперкат
1
+1 за то, почему «это не то же самое, что иметь глобальные функции». Это не было решено другими. (Хотя, если у вас есть несколько колод, глобальная функция все равно будет возвращать разные значения для разных экземпляров одной и той же карты).
Алексис
@supercat Это достойно отдельного вопроса или сеанса чата, но в настоящее время я не заинтересован ни в :-( Вы говорите (в C #), что «структуры, которые реализуют интерфейсы, ведут себя не так, как объекты классов, которые делают то же самое». Я согласен, что есть и другие различия в поведении , чтобы рассмотреть, но AFAIK в коде следующее Interface iObj = (Interface)obj;поведение iObjне влияет structили classстатус obj( за исключением того , что это будет штучной копия на это назначение , если это struct).
Mark Hurd
150

Основная идея ООП состоит в том, что данные и поведение (на основе этих данных) неразделимы, и они связаны идеей объекта класса.

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

Предположим, у вас есть общий API для карточных игр. У вас есть карта класса. Теперь этот класс карт должен определить видимость для игроков.

ХОРОШЕГО НЕБЕСА НЕТ. Когда вы играете в Бридж, вы спрашиваете семерых сердец, когда пришло время изменить руку манекена из секрета, известного только манекену, на то, чтобы его знали все? Конечно, нет. Это не касается карты вообще.

Одним из способов является использование логического значения isVisible (Player p) для класса Card. Другой вариант - иметь логическое значение isVisible (Card c) в классе Player.

Оба ужасны; не делай ни того, ни другого Ни игрок, ни карта не несут ответственности за выполнение правил Bridge!

Вместо этого я выбрал третий вариант, где у нас есть класс Viewport, который, учитывая игрока и список карт, определяет, какие карты видны.

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

Однако такой подход лишает классы Card и Player возможной функции-члена.

Хороший!

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

Нет; Основная идея ООП состоит в том, что объекты инкапсулируют их проблемы . В вашей системе карточку мало волнует. Ни один игрок. Это потому, что вы точно моделируете мир . В реальном мире свойства карт, которые имеют отношение к игре, чрезвычайно просты. Мы могли бы заменить картинки на карточках цифрами от 1 до 52 без особых изменений в игре. Мы могли бы заменить четырех человек манекенами с надписью Север, Юг, Восток и Запад без особых изменений в игре. Игроки и карты - самые простые вещи в мире карточных игр. Правила - это то, что сложно, поэтому класс, представляющий правила, - это то, где должно быть осложнение.

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

Вот как я бы спроектировал вашу систему.

Прежде всего, карты удивительно сложны, если есть игры с более чем одной колодой. Вы должны рассмотреть вопрос: могут ли игроки различать две карты одного ранга? Если игрок один разыгрывает одно из семи червей, а затем происходит какое-то событие, а затем игрок два разыгрывает одно из семи черв, может ли игрок третьего определить, что это были те же самые семь червей? Обдумайте это внимательно. Но помимо этого, карты должны быть очень простыми; они просто данные.

Далее, какова природа игрока? Игрок потребляет последовательность видимых действий и производит в действие .

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

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

А затем просит игрока о действии.

  • Первый игрок, что ты хочешь делать?
  • Игрок первый говорит: тройной верх.
  • Игрок первый, это незаконное действие, потому что тройной топ производит неоправданный гамбит.
  • Первый игрок, что ты хочешь делать?
  • Первый игрок говорит: откажитесь от пиковой дамы.
  • Второй игрок, первый игрок отбросил пиковую даму.

И так далее.

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

Эрик Липперт
источник
41
@gnat: противоположное мнение Алана Кея звучит так: «На самом деле я придумал термин« объектно-ориентированный », и могу сказать, что я не имел в виду C ++.» Существуют ОО-языки без классов; JavaScript приходит на ум.
Эрик Липперт
19
@gnat: Я бы согласился с тем, что JS в его нынешнем виде не является хорошим примером языка ООП, но он показывает, что можно легко построить язык ОО без классов. Я согласен, что фундаментальной единицей OO-сущности в Eiffel и C ++ является класс; Я не согласен с тем, что классы являются непременным условием ОО. Непременное ОО являются объектами , которые инкапсулируют поведение и общаются друг с другом с помощью четко определенного общего интерфейса.
Эрик Липперт
16
Я согласен с @EricLippert, что классы не являются фундаментальными для ОО и наследования, что бы ни говорил основной поток. Инкапсуляция данных, поведения и ответственности, однако, это достигается. Есть прототип на основе языков за пределами Javascript которые OO но бесклассовое. Особенно ошибочно концентрироваться на наследовании этих понятий. Тем не менее, классы являются очень полезным средством организации инкапсуляции поведения. То, что вы можете рассматривать классы как объекты (и в языках-прототипах, наоборот), делает линию размытой.
Шверн
6
Рассмотрим это так: какое поведение реальная карта в реальном мире демонстрирует самостоятельно? Я думаю, что ответ "нет". Другие вещи действуют на карту. Сама карта, в реальном мире, буквально является только информацией (4 из клубов), без какого-либо внутреннего поведения. То, как эта информация (иначе говоря, «карта») используется на 100%, зависит от чего-то / кого-то еще, так называемых «правил» и «игроков». Одни и те же карты могут быть использованы для бесконечного (ну, может быть, не совсем) разнообразия различных игр любым количеством разных игроков. Карта - это просто карта, и все, что у нее есть, это свойства.
Крейг
5
@Montagist: Позвольте мне немного прояснить ситуацию. Рассмотрим С. Я думаю, вы согласитесь, что в С нет классов. Однако вы можете сказать, что структуры - это «классы», вы можете создавать поля типа указателя на функцию, вы можете создавать таблицы, вы можете создавать методы с именем «конструкторы», которые устанавливают таблицы так, что некоторые структуры «наследуются» друг от друга, и так далее. Вы можете эмулировать наследование на основе классов в C. И вы можете эмулировать его в JS. Но сделать это означает создать что-то поверх языка, которого там еще нет.
Эрик Липперт
29

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

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

Хотя ООП имеет некоторые явные преимущества (особенно перед простым процедурным программированием), наивно стремиться к «чистому» ООП. Некоторые проблемы плохо соответствуют объектно-ориентированному подходу и легче решаются другими парадигмами. При возникновении такой проблемы не настаивайте на низком подходе.

  • Рассмотрим расчет последовательности Фибоначчи объектно-ориентированным способом . Я не могу придумать разумного способа сделать это; Простое структурированное программирование предлагает лучшее решение этой проблемы.

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

    static boolean isVisible(Card c, Player p);
    

    и нет ничего плохого в том, Cardчто у нас нет методов rankи методов suitдоступа.

Амон
источник
11
@ Да, это моя точка зрения, и в этом нет ничего плохого. Используйте правильную парадигму <del> language </ del> для данной работы. (Кстати, большинство языков, кроме Smalltalk, не являются чисто объектно-ориентированными. Например, Java, C # и C ++ поддерживают императивное, структурированное, процедурное, модульное, функциональное и объектно-ориентированное программирование. Все эти не OO-парадигмы доступны по причине : так что вы можете их использовать)
amon
1
Есть разумный ОО способ сделать Фибоначчи, вызвать fibonacciметод на экземпляре integer. Я хотел бы подчеркнуть вашу точку зрения, что ОО касается инкапсуляции, даже в, казалось бы, небольших местах. Пусть целое число выяснит, как сделать работу. Позже вы сможете улучшить реализацию, добавить кеширование для повышения производительности. В отличие от функций, методы следуют за данными, поэтому все вызывающие стороны получают выгоду от улучшенной реализации. Возможно, позже будут добавлены целые числа произвольной точности, они могут быть прозрачно обработаны как обычные целые числа и могут иметь собственный fibonacciметод настройки производительности .
Шверн
2
@Schwern, если что-либо Fibonacciявляется подклассом абстрактного класса Sequence, последовательность используется любым набором чисел и отвечает за хранение начальных значений , состояния, кэширования и итератора.
Джордж Райт
2
Я не ожидал, что «чистый ООП Фибоначчи» будет настолько эффективен в деле снайпера . Пожалуйста, прекратите любое круговое обсуждение этого в этих комментариях, хотя это имело определенную развлекательную ценность. Теперь давайте все сделаем что-то конструктивное для разнообразия!
Амон
3
было бы глупо делать Фибоначчи методом целых чисел, просто чтобы вы могли сказать, что это ООП. Это функция и должна рассматриваться как функция.
user253751
19

Основная идея ООП состоит в том, что данные и поведение (на основе этих данных) неразделимы, и они связаны идеей объекта класса. У объекта есть данные и методы, которые работают с этим (и другими данными). Очевидно, что по принципам ООП объекты, представляющие собой просто данные (например, структуры C), считаются анти-паттернами. (...) Это явно противоречит основной идее ООП.

Это сложный вопрос, потому что он основан на нескольких ошибочных предпосылках:

  1. Идея о том, что ООП является единственным допустимым способом написания кода.
  2. Идея, что ООП является четко определенной концепцией. Это стало таким модным словом, что трудно найти двух людей, которые могут договориться о том, что такое ООП.
  3. Идея ООП заключается в объединении данных и поведения.
  4. Идея, что все есть / должно быть абстракцией.

Я не буду особо затрагивать № 1-3, потому что каждый может породить свой собственный ответ, и это вызывает много дискуссий на основе мнений. Но я считаю, что идея «ООП - это соединение данных и поведения» вызывает особую тревогу. Мало того, что это приводит к # 4, это также приводит к идее, что все должно быть методом.

Существует разница между операциями, которые определяют тип, и способами, которыми вы можете использовать этот тип. Возможность извлечения iэлемента th необходима для концепции массива, но сортировка - это только одна из многих вещей, которые я могу выбрать для одного. Сортировка не должна быть методом больше, чем «создать новый массив, содержащий только четные элементы».

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

Вы не сделаете isVisibleметод такого Cardтипа, потому что видимость, вероятно, не важна для вашего представления о карте (если у вас нет очень специальных карт, которые могут стать полупрозрачными или непрозрачными ...). Должен ли это быть метод Playerтипа? Ну, это, вероятно, тоже не определяющее качество игроков. Это должно быть частью какого-то Viewportтипа? Еще раз, это зависит от того, что вы определяете для видового экрана, и является ли понятие проверки видимости карт неотъемлемой частью определения видового экрана.

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

Doval
источник
1
+1 за здравый смысл вместо бессмысленного гудения.
правостороннее
Из строк, которые я прочитал, сочинение, которое вы связали, выглядит как одно прочное прочтение, которого у меня давно не было.
Артур Гавличек
@ArthurHavlicek Труднее следовать, если вы не понимаете языки, использованные в примере кода, но я нашел это довольно ярким.
Доваль
9

Очевидно, что по принципам ООП объекты, представляющие собой просто данные (например, структуры C), считаются анти-паттернами.

Нет, это не так. Объекты Plain-Old-Data представляют собой совершенно корректный шаблон, и я ожидаю, что они будут в любой программе, которая работает с данными, которые необходимо сохранить или передать между различными областями вашей программы.

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

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

DougM
источник
5
Не соглашайтесь ни с чем, что вы сказали, но это не отвечает на вопрос вообще. Тем не менее, я виню вопрос больше, чем ответ.
фунтовые
2
Точно, карточки и документы являются просто контейнерами информации даже в реальном мире, и любой «шаблон», который не может с этим справиться, нужно игнорировать.
JeffO
1
Plain-Old-Data objects are a perfectly valid pattern Я не сказал, что они не были, я говорю, что это неправильно, когда они заполняют всю нижнюю половину заявки.
RokL
8

Лично я считаю, что Domain Driven Design помогает внести ясность в эту проблему. Вопрос, который я задаю: как мне описать карточную игру для людей? Другими словами, что я моделирую? Если объект, который я моделирую, действительно включает в себя слово «область просмотра» и концепцию, соответствующую его поведению, то я бы создал объект области просмотра и заставил бы его делать то, что должно логически.

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

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

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

RibaldEddie
источник
Ответ Липперта выше является лучшим примером этой концепции.
Рибальд Эдди
5

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

Как это относится к Card- Playerпроблеме: Создание ViewPortабстракции имеет смысл , если вы думаете , Cardи Playerкак две независимых библиотек (что предполагало бы Playerиногда используются без Card). Тем не менее, я склонен думать, что Playerдержит Cardsи должен предоставить Collection<Card> getVisibleCards ()доступ к ним. Оба эти решения ( ViewPortи мое) лучше, чем предоставление isVisibleв качестве метода Cardили Player, с точки зрения создания понятных отношений кода.

Внешнее решение намного, намного лучше для DocumentId. Есть небольшая мотивация, чтобы заставить (в основном, целое число) зависеть от сложной библиотеки базы данных.

Артур Гавличек
источник
Мне нравится твой блог.
RokL
3

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

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

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

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

  1. Можно ли иметь простые конструкции держателей данных (классы / структуры; мне неважно, что они смоделированы для этого вопроса), которые на самом деле не предлагают большой функциональности?
  2. Если да, каков наилучший или предпочтительный способ их моделирования?
  3. Если нет, как мы можем включить этот счетчик данных в более высокие классы API (включая поведение)

Мой вид:

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

Harsha
источник
3
Проблема в том, что вопрос (и ваш ответ) касаются того, как все делать "в целом". Дело в том, что мы никогда не делаем вещи «в общем». Мы всегда делаем конкретные вещи. Необходимо изучить наши конкретные вещи и сравнить их с нашими требованиями, чтобы определить, являются ли наши конкретные вещи правильными для ситуации.
Джон Сондерс
@JohnSaunders Я чувствую вашу мудрость здесь и согласен в некоторой степени, но для решения проблемы требуется также простой концептуальный подход. В конце концов, вопрос здесь не такой открытый, как кажется. Я думаю, что это правильный вопрос OOD, с которым сталкивается любой дизайнер OO при первоначальном использовании OOP. Что вы берете? Если конкреция помогает, мы могли бы обсудить создание примера по вашему выбору.
Харша
Я не ходил в школу более 35 лет. В реальном мире я нахожу очень мало значения в «концептуальных подходах». Я нахожу опыт, чтобы быть лучшим учителем, чем Мейерс в этом случае.
Джон Сондерс
Я действительно не понимаю, класс для данных против класса для различия поведения. Если вы правильно абстрагируете свои объекты, различий нет. Представьте себе Pointс getX()функцией. Вы можете представить, что он получает один из атрибутов, но он также может читать его с диска или из Интернета. Получение и настройка - это поведение, и иметь классы, которые делают именно это, вполне нормально. Базы данных только получают и устанавливают данные fwiw
Артур Гавличек
@ArthurHavlicek: Знание того, что класс не будет делать, часто так же полезно, как знание того, что он будет делать. Полезно, чтобы в контракте было указано что-то, что он будет вести себя как не что иное, как неизменяемый неизменяемый держатель данных, или как не более чем не подлежащий обмену изменяемый держатель данных.
суперкат
2

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

Я бы предположил, что, вероятно, было бы полезно иметь CardEntityобъект, который заключает эти аспекты карты в отдельные компоненты. Один компонент будет относиться к маркировке на карте (например, «Алмазный король» или «Взрыв лавы; у игроков есть шанс уклониться от АС-3 или же получить урон 2D6»). Кто-то может относиться к уникальному аспекту состояния, например позиции (например, в колоде, в руке Джо или на столе перед Ларри). Третий может относиться к тому, чтобы видеть это (возможно, никто, возможно, один игрок или, возможно, много игроков). Чтобы обеспечить синхронизацию всего, места, где может быть карта, будут инкапсулированы не как простые поля, а как CardSpaceобъекты; чтобы переместить карту в пробел, можно указать ссылку наCardSpaceобъект; затем он удалил бы себя из старого пространства и поместил бы себя в новое пространство).

Явная инкапсуляция слова «кто знает о X» отдельно от «того, что знает X» должна помочь избежать путаницы. Иногда необходимо соблюдать осторожность, чтобы избежать утечек памяти, особенно при многих-многих ассоциациях (например, если новые карты могут появиться и старые карты исчезнут, необходимо убедиться, что оставленные карты не остаются навсегда прикрепленными к каким-либо долгоживущим объектам ) но если наличие ссылок на объект будет формировать соответствующую часть его состояния, для самого объекта будет вполне уместным явно инкапсулировать такую ​​информацию (даже если он делегирует другому классу работу по фактическому управлению им).

Supercat
источник
0

Однако такой подход лишает классы Card и Player возможной функции-члена.

И как это плохо / опрометчиво?

Чтобы использовать аналогию с примером ваших карт, рассмотрите a Car, a, Driverи вам нужно определить, Driverможет ли он управлять Car.

Итак, вы решили, что не хотите, чтобы ваш человек Carзнал, есть ли у Driverнего правильный ключ от машины или нет, и по какой-то неизвестной причине вы также решили, что вы не хотите, чтобы ваши люди Driverзнали о Carклассе (вы не в полном объеме это в вашем первоначальном вопросе). Следовательно, у вас есть промежуточный класс, что-то похожее на Utilsкласс, который содержит метод с бизнес-правилами , чтобы возвратить booleanзначение для вопроса выше.

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

ХИК
источник