В унаследованном коде я иногда вижу классы, которые являются ничем иным, как обертками для данных. что-то типа:
class Bottle {
int height;
int diameter;
Cap capType;
getters/setters, maybe a constructor
}
Мое понимание ОО состоит в том, что классы являются структурами для данных и методами работы с этими данными. Это, кажется, исключает объекты этого типа. Для меня они не более чем structs
и своего рода поражение цели ОО. Я не думаю, что это обязательно зло, хотя это может быть запахом кода.
Есть ли такой случай, когда такие объекты были бы необходимы? Если это часто используется, делает ли это подозрительным дизайн?
Ответы:
Определенно, не зло и не кодовый запах в моей голове. Контейнеры данных являются действительными гражданами ОО. Иногда вы хотите инкапсулировать связанную информацию вместе. Намного лучше иметь такой метод, как
чем
Использование класса также позволяет добавить дополнительный параметр в Bottle без изменения каждого вызывающего объекта DoStuffWithBottle. И вы можете создать подкласс Bottle и еще больше повысить читабельность и организацию вашего кода, если это необходимо.
Есть также простые объекты данных, которые могут быть возвращены, например, в результате запроса к базе данных. Я полагаю, что термин для них в этом случае - «Объект передачи данных».
В некоторых языках есть и другие соображения. Например, в C # классы и структуры ведут себя по-разному, поскольку структуры являются типом значения, а классы являются ссылочными типами.
источник
DoStuffWith
должен быть метод из кBottle
классу в ООП (и , скорее всего , должны быть неизменны, тоже). То, что вы написали выше, не является хорошим шаблоном в языке OO (если вы не взаимодействуете с устаревшим API). Это является , однако, действительный дизайн в среде без ОО.Классы данных действительны в некоторых случаях. DTO - один хороший пример, упомянутый Анной Лир. В общем, вы должны рассматривать их как семя класса, методы которого еще не выросли. И если вы сталкиваетесь со многими из них в старом коде, относитесь к ним как к сильному запаху кода. Они часто используются старыми программистами на C / C ++, которые никогда не переходили на ОО-программирование и являются признаком процедурного программирования. Постоянное использование геттеров и сеттеров (или, что еще хуже, прямого доступа к частным лицам) может привести к проблемам до того, как вы об этом узнаете. Рассмотрим пример внешнего метода, для которого требуется информация
Bottle
.Вот
Bottle
класс данных):Здесь мы дали
Bottle
некоторое поведение):Первый метод нарушает принцип «скажи-не-спрашивай», и, оставаясь
Bottle
тупым, мы позволяем неявным знаниям о бутылках, таких как то, что заставляет одного хрупкого (Cap
) делиться , в логику, которая находится внеBottle
класса. Вы должны быть в тонусе, чтобы предотвратить такого рода «утечки», когда вы обычно полагаетесь на добытчиков.Второй метод запрашивает
Bottle
только то, что ему нужно для выполнения своей работы, и оставляетBottle
решение, является ли он хрупким или больше заданного размера. Результатом является гораздо более слабая связь между методом и реализацией Bottle. Приятным побочным эффектом является то, что метод более чистый и выразительный.Вы редко будете использовать это множество полей объекта без написания логики, которая должна находиться в классе с этими полями. Выясните, что это за логика, а затем переместите ее туда, где она принадлежит
источник
Если это то, что вам нужно, это нормально, но, пожалуйста, пожалуйста, сделайте это как
вместо чего-то вроде
Пожалуйста.
источник
Как сказала Анна, определенно не зло. Конечно, вы можете помещать операции (методы) в классы, но только если хотите . Вам не нужно .
Позвольте мне немного разобраться с идеей, что вы должны помещать операции в классы, наряду с идеей, что классы являются абстракциями. На практике это побуждает программистов
Создайте больше классов, чем нужно (избыточная структура данных). Когда структура данных содержит больше компонентов, чем минимально необходимо, она не нормализуется и, следовательно, содержит несовместимые состояния. Другими словами, когда это изменяется, это должно быть изменено в больше чем одном месте, чтобы остаться последовательным. Невыполнение всех согласованных изменений делает его несогласованным и является ошибкой.
Решите проблему 1, добавив методы уведомлений , чтобы при изменении части A она пыталась распространить необходимые изменения на части B и C. Это основная причина, по которой рекомендуется использовать методы доступа get-and-set. Поскольку это рекомендуемая практика, она, по-видимому, оправдывает проблему 1, вызывая еще больше проблемы 1 и больше решения 2. Это приводит не только к ошибкам из-за неполной реализации уведомлений, но и к проблеме снижения производительности уведомлений о сбегах. Это не бесконечные вычисления, а просто очень длинные.
Эти понятия преподаются как хорошие вещи, как правило, учителями, которым не приходилось работать в приложениях с миллионами строк монстров, изобилующих этими проблемами.
Вот что я пытаюсь сделать:
Сохраняйте данные как можно более нормализованными, чтобы при внесении изменений в данные было сделано как можно меньше кодовых точек, чтобы минимизировать вероятность входа в несогласованное состояние.
Когда данные должны быть ненормализованы, а избыточность неизбежна, не используйте уведомления в попытке сохранить их согласованность. Скорее терпеть временное несоответствие. Устранить несогласованность с периодической разверткой данных с помощью процесса, который делает только это. Это централизует ответственность за поддержание согласованности, избегая проблем с производительностью и правильностью, к которым склонны уведомления. Это приводит к тому, что код намного меньше, без ошибок и эффективен.
источник
Этот вид классов весьма полезен, когда вы имеете дело с приложениями среднего / большого размера по ряду причин:
Подводя итог, по моему опыту, они обычно более полезны, чем раздражают.
источник
При разработке игры накладные расходы на 1000 вызовов функций и слушателей событий иногда могут стоить того, чтобы иметь классы, которые только хранят данные, и иметь другие классы, которые перебирают все классы только для данных для выполнения логики.
источник
Согласитесь с Анной Лир,
Иногда люди забывают прочитать Соглашения о кодировании Java 1999 года, в которых очень ясно сказано, что этот вид программирования идеально подходит. На самом деле, если вы избегаете этого, ваш код будет пахнуть! (слишком много получателей / установщиков)
Из Java Code Conventions 1999: один пример соответствующих общедоступных переменных экземпляра - это случай, когда класс по сути является структурой данных без какого-либо поведения. Другими словами, если бы вы использовали структуру вместо класса (если Java поддерживает структуру), то целесообразно сделать переменные экземпляра класса общедоступными. http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-137265.html#177
При правильном использовании POD (простые старые структуры данных) лучше, чем POJO, точно так же, как POJO часто лучше, чем EJB.
http://en.wikipedia.org/wiki/Plain_Old_Data_Structures
источник
Структуры имеют свое место, даже в Java. Вы должны использовать их, только если выполняются следующие две вещи:
Если это так, то вы должны сделать поля общедоступными и пропустить методы получения / установки. Геттеры и сеттеры в любом случае неуклюжи, а Java глупа из-за отсутствия свойств, подобных полезному языку. Так как ваш структурный объект не должен иметь никаких методов, открытые поля имеют больше смысла.
Однако, если один из них не подходит, вы имеете дело с реальным классом. Это означает, что все поля должны быть приватными. (Если вам абсолютно необходимо поле в более доступной области, используйте метод получения / установки.)
Чтобы проверить, имеет ли ваша предполагаемая структура поведение, посмотрите, когда используются поля. Если это кажется нарушением, скажи, не спрашивай , тогда тебе нужно перенести это поведение в свой класс.
Если некоторые из ваших данных не должны измениться, вам нужно сделать все эти поля окончательными. Вы можете сделать свой класс неизменным . Если вам нужно проверить ваши данные, тогда предоставьте проверку в установщиках и конструкторах. (Полезный трюк состоит в том, чтобы определить частный установщик и изменить поле в вашем классе, используя только этот установщик.)
Ваш пример с бутылкой, скорее всего, провалит оба теста. Вы могли бы иметь (надуманный) код, который выглядит так:
Вместо этого должно быть
Если бы вы изменили высоту и диаметр, это была бы та же самая бутылка? Возможно нет. Это должно быть окончательным. В порядке ли отрицательное значение для диаметра? Должна ли ваша бутылка быть выше, чем она широкая? Может ли Cap быть нулевым? Нет? Как вы это проверяете? Предположим, клиент либо глуп, либо злой. ( Невозможно определить разницу. ) Вам необходимо проверить эти значения.
Вот как может выглядеть ваш новый класс Bottle:
источник
ИМХО, часто не хватает таких классов в сильно объектно-ориентированных системах. Мне нужно тщательно квалифицировать это.
Конечно, если поля данных имеют широкий охват и видимость, это может быть крайне нежелательно, если в вашей кодовой базе существуют сотни или тысячи мест, подделывающих такие данные. Это требует хлопот и трудностей с сохранением инвариантов. Однако в то же время это не означает, что каждый отдельный класс во всей кодовой базе извлекает выгоду из сокрытия информации.
Но во многих случаях такие поля данных будут иметь очень узкую область действия. Очень простой пример - закрытый
Node
класс структуры данных. Часто он может значительно упростить код, уменьшая количество происходящих взаимодействий между объектами, если ониNode
могут просто состоять из необработанных данных. Это служит расцепления механизма , так как альтернативного варианта может потребоваться двунаправленную связь с, скажем,Tree->Node
иNode->Tree
в отличие от простоTree->Node Data
.Более сложный пример - системы с компонентами сущностей, которые часто используются в игровых движках. В этих случаях компоненты часто являются просто необработанными данными и классами, подобными тем, которые вы показали. Однако их область действия / видимость имеет тенденцию быть ограниченной, поскольку обычно есть только одна или две системы, которые могут получить доступ к этому конкретному типу компонента. В результате вы все еще склонны считать, что поддерживать инварианты в этих системах довольно легко, и, кроме того, такие системы имеют очень мало
object->object
взаимодействий, что позволяет очень легко понять, что происходит на уровне птичьего полета.В таких случаях вы можете получить нечто более похожее на взаимодействие (эта диаграмма показывает взаимодействия, а не связь, так как диаграмма связи может включать абстрактные интерфейсы для второго изображения ниже):
... в отличие от этого:
... и первый тип системы, как правило, гораздо проще поддерживать и рассуждать с точки зрения корректности, несмотря на тот факт, что зависимости фактически перетекают в данные. Вы получаете гораздо меньше связей, в первую очередь потому, что многие вещи можно превратить в необработанные данные вместо объектов, взаимодействующих друг с другом, образуя очень сложный график взаимодействий.
источник
В мире ООП существует даже много странных существ. Однажды я создал класс, который является абстрактным и ничего не содержит, он просто является общим родителем двух других классов ... это класс Port:
Таким образом, SceneMember является базовым классом, он выполняет всю работу, которая требуется для появления на сцене: добавление, удаление, получение-установка идентификатора и т. Д. Сообщение - это соединение между портами, оно имеет свою собственную жизнь. InputPort и OutputPort содержат собственные функции ввода / вывода.
Порт пуст. Он просто используется для группировки InputPort и OutputPort вместе, скажем, для списка портов. Он пуст, потому что SceneMember содержит все, что нужно для действия в качестве члена, а InputPort и OutputPort содержат задачи, заданные портом. InputPort и OutputPort настолько различны, что не имеют общих (кроме SceneMember) функций. Итак, Порт пуст. Но это законно.
Может быть, это антипаттерн , но мне это нравится.
(Это похоже на слово «мат», которое используется как для «жены», так и для «мужа». Вы никогда не используете слово «мат» для конкретного человека, потому что вы знаете его пол, и это не имеет значения если он / она женат или нет, то вместо этого вы используете «кого-то», но оно все еще существует и используется в редких, абстрактных ситуациях, например, в тексте закона.)
источник