Айк Григорян
В этой статье исследуются особенности и способы использования объектов и структур данных. Он предназначен для программистов с базовыми знаниями и опытом, желающих изучить связь и разницу между этими двумя категориями.
Абстракция данных
Обратите внимание на разницу между листингом 1 и листингом 2. Оба представляют данные точки на декартовой плоскости. И все же один раскрывает его реализацию, а другой полностью скрывает.
В листинге 2 замечательно то, что невозможно определить, в прямоугольных или полярных координатах реализована реализация. Может быть и то, и другое! И все же интерфейс безошибочно представляет собой структуру данных.
Но он представляет собой нечто большее, чем просто структуру данных. Методы применяют политику доступа. Вы можете считывать отдельные координаты независимо, но вы должны установить координаты вместе как атомарную операцию.
Листинг 1, с другой стороны, очень четко реализован в прямоугольных координатах и заставляет нас манипулировать этими координатами независимо.
Скрытие реализации - это не просто вопрос размещения между переменными слоя функций. Реализация сокрытия - это абстракции! Класс не просто выталкивает свои переменные через геттеры и сеттеры. Скорее он предоставляет абстрактные интерфейсы, которые позволяют пользователям манипулировать сущностью данных, не зная его реализации.
Рассмотрим листинг 3 и листинг 4. Первый использует конкретные термины для обозначения уровня топлива в транспортном средстве, тогда как второй использует абстракцию в процентах.
В конкретном случае вы можете быть уверены, что это всего лишь методы доступа к переменным. В абстрактном случае вы вообще не имеете ни малейшего представления о форме данных.
В обоих случаях предпочтительнее второй вариант. Мы не хотим раскрывать детали наших данных.
Скорее мы хотим выразить наши данные в абстрактных терминах. Это достигается не только с помощью интерфейсов и / или геттеров и сеттеров.
Антисимметрия данных / объектов
Эти два примера показывают разницу между объектами и структурами данных. Объекты скрывают свои данные за абстракциями и предоставляют функции, которые работают с этими данными. Структура данных раскрывает свои данные и не имеет значимых функций.
Рассмотрим, например, пример процедурной формы в листинге 5. Класс Geometry работает с тремя классами форм. Классы форм - это простые структуры данных без какого-либо поведения. Все поведение находится в классе Geometry.
Объектно-ориентированные программисты могут морщить нос при этом и жаловаться, что это процедурно - и они будут правы.
Подумайте, что произойдет, если в Geometry добавить функцию perimeter (). Классы форм не пострадают! Любые другие классы, зависящие от форм, также не пострадают!
С другой стороны, если я добавлю новую форму, я должен изменить все функции в геометрии, чтобы справиться с ней.
Теперь рассмотрим объектно-ориентированное решение в листинге 6. Здесь метод area () полиморфен. Класс геометрии не требуется. Поэтому, если я добавлю новую фигуру, ни одна из существующих функций не будет затронута, но если я добавлю новую функцию, все фигуры должны быть изменены!
Опять же, мы видим взаимодополняющий характер этих двух определений; они виртуальные противоположности! Это раскрывает фундаментальную дихотомию между объектами и структурами данных:
Процедурный код (код, использующий структуры данных) позволяет легко добавлять новые функции без изменения существующих структур данных. С другой стороны, объектно-ориентированный код упрощает добавление новых классов без изменения существующих функций.
Дополнение также верно:
Процедурный код затрудняет добавление новых структур данных, потому что все функции должны измениться. ОО-код затрудняет добавление новых функций, потому что все классы должны измениться.
Итак, то, что сложно для объектно-ориентированного программирования, легко для процедур, а то, что сложно для процедур, легко для объектно-ориентированного программирования!
В любой сложной системе будут моменты, когда мы захотим добавить новые типы данных а не новые функции. Для этих случаев наиболее подходят объекты и объектно-ориентированные объекты. С другой стороны, также будут моменты, когда мы захотим добавить новые функции, а не типы данных. В этом случае процедурный код и структуры данных будут более подходящими.
Опытные программисты знают, что представление о том, что все является объектом, является мифом. Иногда вам действительно действительно нужны простые структуры данных с оперирующими с ними процедурами.
Закон Деметры
Существует хорошо известная эвристика под названием Закон Деметры, согласно которой модуль не должен знать о внутренностях объектов, которыми он манипулирует. Как мы видели в предыдущем разделе, объекты скрывают свои данные и предоставляют операции. Это означает, что объект не должен раскрывать свою внутреннюю структуру через методы доступа, потому что это означает раскрытие, а не скрытие своей внутренней структуры.
Точнее, Закон Деметры гласит, что метод f класса C должен вызывать только следующие методы:
• C
• Объект, созданный f
• Объект, переданный в качестве аргумента функции f
- Объект, содержащийся в переменной экземпляра C
Этот метод не должен не вызывать методы для объектов, возвращаемых какой-либо из разрешенных функций. Другими словами, разговаривайте с друзьями, а не с незнакомцами.
Следующий код 3, похоже, нарушает закон Деметры (среди прочего), потому что он вызывает функцию getScratchDir () для возвращаемого значения getOptions (), а затем вызывает getAbsolutePath () для возвращаемого значения getScratchDir (). final String outputDir = ctxt.getOptions (). getScratchDir (). getAbsolutePath ();
Обломки поездов
Такой код часто называют разваливанием поезда, потому что он выглядит как связка спаренных вагонов. Подобные цепочки вызовов обычно считаются небрежным стилем, и их следует избегать [G36]. Обычно лучше всего разделить их следующим образом:
Параметры opts = ctxt.getOptions ();
Файл scratchDir = opts.getScratchDir ();
final String outputDir = scratchDir.getAbsolutePath ();
Конечно, содержащий модуль знает, что объект ctxt содержит параметры, которые содержат временный каталог с абсолютным путем. Это большой объем знаний для одной функции. Вызывающая функция знает, как перемещаться по множеству различных объектов. Является ли это нарушением Demeter, зависит от того, являются ли ctxt, Options и ScratchDir объектами или структурами данных. Если они являются объектами, то их внутренняя структура должна быть скрыта, а не обнажена, и поэтому знание их внутренностей является явным нарушением Закона Деметры. С другой стороны, если ctxt, Options и ScratchDir - это просто структуры данных без поведения, тогда они естественным образом раскрывают свою внутреннюю структуру, и поэтому Demeter не применяется.
Использование функций доступа усложняет проблему. Если бы код был написан следующим образом, то, вероятно, мы бы не спрашивали о нарушениях Деметры. final String outputDir = ctxt.options.scratchDir.absolutePath;
Эта проблема была бы намного менее запутанной, если бы в структурах данных просто были общедоступные переменные и не было функций, тогда как у объектов были частные переменные и общедоступные функции. Однако существуют структуры и стандарты (например, «bean-компоненты»), которые требуют, чтобы даже простые структуры данных имели средства доступа и мутаторы.
Гибриды
Эта путаница иногда приводит к неудачным гибридным структурам, которые наполовину представляют собой объект и наполовину структуру данных. У них есть функции, которые делают важные вещи, и у них также есть либо общедоступные переменные, либо общедоступные методы доступа и мутаторы, которые для всех намерений и целей делают частные переменные общедоступными, соблазняя другие внешние функции использовать эти переменные так же, как процедурная программа будет использовать структура данных. Такие гибриды затрудняют добавление новых функций, но также затрудняют добавление новых структур данных. Они худшие из обоих миров. Избегайте их создания. Они указывают на запутанный дизайн, авторы которого не уверены - или, что еще хуже, не знают - нужна ли им защита от функций или типов.
Скрытие структуры
Что, если ctxt, options и scratchDir - это объекты с реальным поведением? Затем, поскольку предполагается, что объекты скрывают свою внутреннюю структуру, мы не сможем перемещаться по ним. Как же тогда получить абсолютный путь к каталогу с нуля?
ctxt.getAbsolutePathOfScratchDirectoryOption ();
or
ctx.getScratchDirectoryOption (). getAbsolutePath ()
Первый вариант может привести к взрывному росту методов в объекте ctxt. Второй предполагает, что getScratchDirectoryOption () возвращает структуру данных, а не объект. Ни один из вариантов не подходит.
Если ctxt является объектом, мы должны сказать ему, чтобы он что-то сделал; мы не должны спрашивать его о его внутреннем устройстве. Так зачем нам нужен абсолютный путь к каталогу с нуля? Что мы собирались с этим делать? Рассмотрим этот код (на много строк ниже) того же модуля:
Строка outFile = outputDir + «/» + className.replace ('.', '/') + «.Class»;
FileOutputStream fout = new FileOutputStream (outFile);
BufferedOutputStream bos = new BufferedOutputStream ( fout);
Смешение разных уровней детализации немного беспокоит. Точки, косые черты, расширения файлов и объекты File не следует так небрежно смешивать вместе с окружающим кодом. Однако, игнорируя это, мы видим, что целью получения абсолютного пути к рабочему каталогу было создание рабочего файла с заданным именем.
Итак, что, если бы мы сказали объекту ctxt сделать это?
BufferedOutputStream bos = ctxt.createScratchFileStream (имя_класса);
Это кажется разумным поступком для объекта! Это позволяет ctxt скрывать свои внутренние компоненты и предотвращает нарушение текущей функции закона Деметры, перемещаясь по объектам, о которых она не должна знать.
Объекты передачи данных
Типичная форма структуры данных - это класс с общедоступными переменными и без функций. Иногда это называют объектом передачи данных или DTO. Они часто становятся первыми в серии этапов трансляции, на которых необработанные данные в базе данных преобразуются в объекты в коде приложения.
Несколько более распространенной является форма «bean», показанная в Листинге 6–7 . Бины имеют частные переменные, которыми манипулируют геттеры и сеттеры.
Активные записи - это особые формы DTO. Это структуры данных с общедоступными (или доступными) переменными, но обычно у них есть методы навигации, такие как save и find. Обычно эти Active Records являются прямым переводом из таблиц базы данных или других источников данных.
К сожалению, мы часто обнаруживаем, что разработчики пытаются рассматривать эти структуры данных, как если бы они были объектами, помещая методы бизнес-правил в их. Это неудобно, поскольку создает гибрид между структурой данных и объектом.
Решение, конечно же, состоит в том, чтобы рассматривать Active Record как структуру данных и создавать отдельные объекты, содержащие бизнес-правила и скрывающие их внутренние данные (которые, вероятно, являются экземплярами Active Record).
Заключение
Объекты раскрывают поведение и скрывают данные. Это упрощает добавление новых типов объектов без изменения существующего поведения. Это также затрудняет добавление нового поведения к существующим объектам. Структуры данных предоставляют данные и не имеют существенного поведения. Это упрощает добавление нового поведения к существующим структурам данных, но затрудняет добавление новых структур данных к существующим функциям.
В любой данной системе нам иногда требуется гибкость для добавления новых типов данных, поэтому мы предпочитаем объекты для этой части системы. В других случаях нам потребуется гибкость для добавления новых моделей поведения, поэтому в этой части системы мы предпочитаем типы данных и процедуры.
Хорошие разработчики программного обеспечения понимают эти проблемы без предубеждений и выбирают подход, который лучше всего подходит для работы. под рукой.
Это влияет на функции, но если я добавлю новую функцию, все фигуры должны быть изменены! 1