Основная идея ООП состоит в том, что данные и поведение (на основе этих данных) неразделимы, и они связаны идеей объекта класса. У объекта есть данные и методы, которые работают с этим (и другими данными). Очевидно, что по принципам ООП объекты, представляющие собой просто данные (например, структуры 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
класс мертвым, без поведения, как структурный класс.
источник
Ответы:
То, что вы описываете, называется моделью анемичной области . Как и во многих принципах проектирования ООП (таких как Закон Деметры и т. Д.), Не стоит отклоняться назад, просто чтобы выполнить правило.
Нет ничего плохого в том, чтобы иметь мешки с ценностями, если они не загромождают весь ландшафт и не полагаются на другие предметы для ведения домашнего хозяйства, которое они могли бы делать для себя .
Конечно, было бы запахом кода, если бы у вас был отдельный класс только для изменения свойств
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 #.Видеть
http://msdn.microsoft.com/en-us/library/ah19swz4.aspx
http://msdn.microsoft.com/en-us/library/0taef578.aspx
для справки. Это очень объектно-ориентированный язык, но никто не делает из этого большого дела.
источник
Interface iObj = (Interface)obj;
поведениеiObj
не влияетstruct
илиclass
статусobj
( за исключением того , что это будет штучной копия на это назначение , если этоstruct
).Вы делаете общую ошибку, предполагая, что классы являются фундаментальной концепцией в ООП. Классы являются лишь одним из наиболее популярных способов достижения инкапсуляции. Но мы можем позволить этому скользить.
ХОРОШЕГО НЕБЕСА НЕТ. Когда вы играете в Бридж, вы спрашиваете семерых сердец, когда пришло время изменить руку манекена из секрета, известного только манекену, на то, чтобы его знали все? Конечно, нет. Это не касается карты вообще.
Оба ужасны; не делай ни того, ни другого Ни игрок, ни карта не несут ответственности за выполнение правил Bridge!
Я никогда раньше не играл в карты с «окном просмотра», поэтому понятия не имею, что этот класс должен инкапсулировать. Я уже играл в карты с парой колод карт, некоторые игроки, стол, и копию Хойл. Какую из этих вещей представляет Viewport?
Хороший!
Нет; Основная идея ООП состоит в том, что объекты инкапсулируют их проблемы . В вашей системе карточку мало волнует. Ни один игрок. Это потому, что вы точно моделируете мир . В реальном мире свойства карт, которые имеют отношение к игре, чрезвычайно просты. Мы могли бы заменить картинки на карточках цифрами от 1 до 52 без особых изменений в игре. Мы могли бы заменить четырех человек манекенами с надписью Север, Юг, Восток и Запад без особых изменений в игре. Игроки и карты - самые простые вещи в мире карточных игр. Правила - это то, что сложно, поэтому класс, представляющий правила, - это то, где должно быть осложнение.
Теперь, если один из ваших игроков - ИИ, его внутреннее состояние может быть чрезвычайно сложным. Но этот ИИ не определяет, может ли он видеть карту. Правила определяют это .
Вот как я бы спроектировал вашу систему.
Прежде всего, карты удивительно сложны, если есть игры с более чем одной колодой. Вы должны рассмотреть вопрос: могут ли игроки различать две карты одного ранга? Если игрок один разыгрывает одно из семи червей, а затем происходит какое-то событие, а затем игрок два разыгрывает одно из семи черв, может ли игрок третьего определить, что это были те же самые семь червей? Обдумайте это внимательно. Но помимо этого, карты должны быть очень простыми; они просто данные.
Далее, какова природа игрока? Игрок потребляет последовательность видимых действий и производит в действие .
Объект правил - это то, что координирует все это. Правила производят последовательность видимых действий и информируют игроков:
А затем просит игрока о действии.
И так далее.
Отделите ваши механизмы от вашей политики . Политики игры должны быть заключены в объект политики , а не в карты . Карты - это просто механизм.
источник
Вы правы, что связь данных и поведения является центральной идеей ООП, но это еще не все. Например, инкапсуляция : ООП / модульное программирование позволяют нам отделить открытый интерфейс от деталей реализации. В ООП это означает, что данные никогда не должны быть общедоступными и должны использоваться только через методы доступа. По этому определению объект без каких-либо методов действительно бесполезен.
Класс, который не предлагает никаких методов, кроме методов доступа, по сути является слишком сложной структурой. Но это неплохо, потому что ООП дает вам гибкость в изменении внутренних деталей, чего нет в структуре. Например, вместо сохранения значения в поле члена, оно может каждый раз пересчитываться. Или алгоритм поддержки изменяется, и вместе с ним должно отслеживаться состояние.
Хотя ООП имеет некоторые явные преимущества (особенно перед простым процедурным программированием), наивно стремиться к «чистому» ООП. Некоторые проблемы плохо соответствуют объектно-ориентированному подходу и легче решаются другими парадигмами. При возникновении такой проблемы не настаивайте на низком подходе.
Рассмотрим расчет последовательности Фибоначчи объектно-ориентированным способом . Я не могу придумать разумного способа сделать это; Простое структурированное программирование предлагает лучшее решение этой проблемы.
Ваше
isVisible
отношение принадлежит к обоим классам, или ни к одному, ни фактически: к контексту . Записи без поведения типичны для функционального или процедурного подхода к программированию, который, кажется, лучше всего подходит для вашей проблемы. В этом нет ничего плохогои нет ничего плохого в том,
Card
что у нас нет методовrank
и методовsuit
доступа.источник
fibonacci
метод на экземпляреinteger
. Я хотел бы подчеркнуть вашу точку зрения, что ОО касается инкапсуляции, даже в, казалось бы, небольших местах. Пусть целое число выяснит, как сделать работу. Позже вы сможете улучшить реализацию, добавить кеширование для повышения производительности. В отличие от функций, методы следуют за данными, поэтому все вызывающие стороны получают выгоду от улучшенной реализации. Возможно, позже будут добавлены целые числа произвольной точности, они могут быть прозрачно обработаны как обычные целые числа и могут иметь собственныйfibonacci
метод настройки производительности .Fibonacci
является подклассом абстрактного классаSequence
, последовательность используется любым набором чисел и отвечает за хранение начальных значений , состояния, кэширования и итератора.Это сложный вопрос, потому что он основан на нескольких ошибочных предпосылках:
Я не буду особо затрагивать № 1-3, потому что каждый может породить свой собственный ответ, и это вызывает много дискуссий на основе мнений. Но я считаю, что идея «ООП - это соединение данных и поведения» вызывает особую тревогу. Мало того, что это приводит к # 4, это также приводит к идее, что все должно быть методом.
Существует разница между операциями, которые определяют тип, и способами, которыми вы можете использовать этот тип. Возможность извлечения
i
элемента th необходима для концепции массива, но сортировка - это только одна из многих вещей, которые я могу выбрать для одного. Сортировка не должна быть методом больше, чем «создать новый массив, содержащий только четные элементы».ООП - это использование объектов. Объекты являются лишь одним из способов достижения абстракции . Абстракция - это средство избежать ненужной связи в вашем коде, а не самоцель. Если ваше представление о карте определяется исключительно значением ее набора и ранга, то можно реализовать ее в виде простого кортежа или записи. Там нет несущественных деталей, от которых любая другая часть кода могла бы сформировать зависимость. Иногда вам просто нечего скрывать.
Вы не сделаете
isVisible
метод такогоCard
типа, потому что видимость, вероятно, не важна для вашего представления о карте (если у вас нет очень специальных карт, которые могут стать полупрозрачными или непрозрачными ...). Должен ли это быть методPlayer
типа? Ну, это, вероятно, тоже не определяющее качество игроков. Это должно быть частью какого-тоViewport
типа? Еще раз, это зависит от того, что вы определяете для видового экрана, и является ли понятие проверки видимости карт неотъемлемой частью определения видового экрана.Это очень возможно,
isVisible
просто должна быть бесплатная функция.источник
Нет, это не так. Объекты Plain-Old-Data представляют собой совершенно корректный шаблон, и я ожидаю, что они будут в любой программе, которая работает с данными, которые необходимо сохранить или передать между различными областями вашей программы.
Хотя ваш уровень данных может создавать полный
Player
класс, когда он читает данные изPlayers
таблицы, вместо этого он может быть просто общей библиотекой данных, которая возвращает POD с полями из таблицы, которую он передает в другую область вашей программы, которая преобразует POD игрока в ваш конкретныйPlayer
класс.Использование объектов данных, как типизированных, так и нетипизированных, может не иметь смысла в вашей программе, но это не делает их антишаблоном. Если они имеют смысл, используйте их, а если нет, не используйте.
источник
Plain-Old-Data objects are a perfectly valid pattern
Я не сказал, что они не были, я говорю, что это неправильно, когда они заполняют всю нижнюю половину заявки.Лично я считаю, что Domain Driven Design помогает внести ясность в эту проблему. Вопрос, который я задаю: как мне описать карточную игру для людей? Другими словами, что я моделирую? Если объект, который я моделирую, действительно включает в себя слово «область просмотра» и концепцию, соответствующую его поведению, то я бы создал объект области просмотра и заставил бы его делать то, что должно логически.
Однако, если у меня нет концепции области просмотра в моей игре, и я думаю, что это то, что мне нужно, потому что в противном случае код «кажется неправильным». Я дважды думаю о добавлении в него моей доменной модели.
Слово модель означает, что вы строите представление чего-либо. Я предупреждаю против включения класса, который представляет нечто абстрактное помимо того, что вы представляете.
Я отредактирую, чтобы добавить, что, возможно, вам понадобится концепция Viewport в другой части вашего кода, если вам нужно взаимодействовать с дисплеем. Но с точки зрения DDD это будет проблемой инфраструктуры и будет существовать вне доменной модели.
источник
Обычно я не занимаюсь саморекламой, но на самом деле я много писал о проблемах дизайна ООП в своем блоге . Подводя итог нескольким страницам: вы не должны начинать дизайн с классов. Начиная с интерфейсов или API-интерфейсов и кода формы оттуда, у вас больше шансов предоставить значимые абстракции, соответствовать спецификациям и избежать раздувания конкретных классов с кодом, который нельзя использовать повторно.
Как это относится к
Card
-Player
проблеме: СозданиеViewPort
абстракции имеет смысл , если вы думаете ,Card
иPlayer
как две независимых библиотек (что предполагало быPlayer
иногда используются безCard
). Тем не менее, я склонен думать, чтоPlayer
держитCards
и должен предоставитьCollection<Card> getVisibleCards ()
доступ к ним. Оба эти решения (ViewPort
и мое) лучше, чем предоставлениеisVisible
в качестве методаCard
илиPlayer
, с точки зрения создания понятных отношений кода.Внешнее решение намного, намного лучше для
DocumentId
. Есть небольшая мотивация, чтобы заставить (в основном, целое число) зависеть от сложной библиотеки базы данных.источник
Я не уверен, что на данный вопрос ответили на правильном уровне. Я должен был побудить мудрых на форуме активно обдумать суть вопроса здесь.
У Мэда возникает ситуация, когда он считает, что программирование в соответствии с его пониманием ООП, как правило, приводит к тому, что многие конечные узлы являются держателями данных, в то время как его API верхнего уровня состоит из большей части поведения.
Я думаю, что тема немного коснулась вопроса о том, будет ли isVisible определяться на карте против игрока; это был простой пример, иллюстрированный, хотя и наивный.
Я должен был подтолкнуть опытных здесь, чтобы посмотреть на проблему под рукой, хотя. Я думаю, что есть хороший вопрос, который U Mad задал. Я понимаю, что вы бы подтолкнули правила и соответствующую логику к собственному объекту; но, насколько я понимаю, вопрос
Мой вид:
Я думаю, что вы задаете вопрос о гранулярности, который трудно понять в объектно-ориентированном программировании. По своему небольшому опыту я не включил бы в свою модель сущность, которая сама по себе не имеет никакого поведения. Если бы мне пришлось, я, вероятно, использовал конструкцию struct, предназначенную для хранения такой абстракции, в отличие от класса, который имеет идею инкапсуляции данных и поведения.
источник
Point
сgetX()
функцией. Вы можете представить, что он получает один из атрибутов, но он также может читать его с диска или из Интернета. Получение и настройка - это поведение, и иметь классы, которые делают именно это, вполне нормально. Базы данных только получают и устанавливают данные fwiwОдин из распространенных источников путаницы в ООП связан с тем, что многие объекты инкапсулируют два аспекта состояния: то, что они знают, и то, что знают о них. При обсуждении состояния объектов часто игнорируется последний аспект, поскольку в рамках, где ссылки на объекты являются беспорядочными, нет общего способа определить, что вещи могут знать о каком-либо объекте, ссылка на который когда-либо выставлялась внешнему миру.
Я бы предположил, что, вероятно, было бы полезно иметь
CardEntity
объект, который заключает эти аспекты карты в отдельные компоненты. Один компонент будет относиться к маркировке на карте (например, «Алмазный король» или «Взрыв лавы; у игроков есть шанс уклониться от АС-3 или же получить урон 2D6»). Кто-то может относиться к уникальному аспекту состояния, например позиции (например, в колоде, в руке Джо или на столе перед Ларри). Третий может относиться к тому, чтобы видеть это (возможно, никто, возможно, один игрок или, возможно, много игроков). Чтобы обеспечить синхронизацию всего, места, где может быть карта, будут инкапсулированы не как простые поля, а какCardSpace
объекты; чтобы переместить карту в пробел, можно указать ссылку наCardSpace
объект; затем он удалил бы себя из старого пространства и поместил бы себя в новое пространство).Явная инкапсуляция слова «кто знает о X» отдельно от «того, что знает X» должна помочь избежать путаницы. Иногда необходимо соблюдать осторожность, чтобы избежать утечек памяти, особенно при многих-многих ассоциациях (например, если новые карты могут появиться и старые карты исчезнут, необходимо убедиться, что оставленные карты не остаются навсегда прикрепленными к каким-либо долгоживущим объектам ) но если наличие ссылок на объект будет формировать соответствующую часть его состояния, для самого объекта будет вполне уместным явно инкапсулировать такую информацию (даже если он делегирует другому классу работу по фактическому управлению им).
источник
И как это плохо / опрометчиво?
Чтобы использовать аналогию с примером ваших карт, рассмотрите a
Car
, a,Driver
и вам нужно определить,Driver
может ли он управлятьCar
.Итак, вы решили, что не хотите, чтобы ваш человек
Car
знал, есть ли уDriver
него правильный ключ от машины или нет, и по какой-то неизвестной причине вы также решили, что вы не хотите, чтобы ваши людиDriver
знали оCar
классе (вы не в полном объеме это в вашем первоначальном вопросе). Следовательно, у вас есть промежуточный класс, что-то похожее наUtils
класс, который содержит метод с бизнес-правилами , чтобы возвратитьboolean
значение для вопроса выше.Я думаю, что это хорошо. Промежуточному классу может потребоваться только проверить ключи от автомобиля сейчас, но его можно реорганизовать, чтобы рассмотреть, есть ли у водителя действительные водительские права, под воздействием алкоголя или в будущем в антиутопическом состоянии проверить биометрические данные ДНК. Благодаря инкапсуляции действительно нет больших проблем, когда эти три класса сосуществуют вместе.
источник