Взаимодействие объектов
в рамках некоторого программного модуля
может быть организовано различными способами.
Например, один объект может прямо обращаться
к свойствам другого объекта, посредством
механизма синхронного или асинхронного
вызова, через объявленный интерфейс или
непосредственно. Примеры такого взаимодействия
«объект-объект» можно встретить в литературе.
Однако недостатки такой
формы связи вполне очевидны. Если в коде
одного объекта встречается обращение к
свойству другого объекта, то использовать
эти объекты порознь невозможно. Как следствие,
утрачивается гибкость и управляемость.
Альтернативой для данной связи является
связь через посредство контейнера. В этом
случае, два объекта, принадлежащие одному
контейнеру, не могут взаимодействовать
друг с другом непосредственно. За их взаимодействие
отвечает контейнер. Это позволяет отделить
логику межобъектного взаимодействия от
логики работы самих объектов. В результате
внутренняя логика класса становится существенно
проще, объекты этого класса можно использовать
в любых сочетаниях с объектами других
классов и, наконец, логика управления
вложенными классами обретает независимость
и гибкость. Эта логика взаимодействия
и является логикой контейнера.
Важность данного положения
трудно переоценить. И дело здесь не столько
в том, что повторное использование классов
значительно повышается, сколько в том,
что существенно упрощается процесс разработки
сложных систем и программных комплексов.
Используемые в настоящее время классы
логически просты, никто, пока не создаёт
такие классы, как, скажем, предприятие.
И, вследствие этого необходимость в разделении
логик пока неочевидна. Но сложные составные
классы могут иметь весьма нетривиальную
логическую реализацию. Например, такой
контейнер может находиться в различных
состояниях и, в зависимости от своего
состояния, так или иначе, обрабатывать
входное сообщение. Мало того, изменение
состояния может произойти непосредственно
в процессе обработки сообщения, что может
быть связано с изменением схемы дальнейшей
трансляции. В такой ситуации очень сложно,
или даже вообще невозможно, реализовать
логику межобъектного взаимодействия внутри
вложенных классов.
Инкапсуляция контейнеров
подразумевает, в частности, что контейнер
может эмулировать любой системный сервис
для вложенных объектов. Например, если
одному из вложенных объектов потребовалась
память, то он обращается с запросом на
выделение памяти к своему контейнеру.
Безусловно, каждый контейнер не должен
реализовывать весь системный сервис, но
контейнеру, возможно, потребуется перехват
обращений вложенных объектов за системным
сервисом, например, в целях оптимизации
работы всей совокупности вложенных объектов.
С другой стороны, подобное
решение избавляет разработчиков классов
вложенных объектов от необходимости знать
о том, кто предоставляет тот или иной
сервис. Всё что им необходимо для работы
предоставляет контейнер – «хозяин» этих
вложенных объектов. При загрузке или создании
контейнера система определяет, будет ли
контейнер перехватывать запросы вложенных
объектов или эти запросы необходимо передать
службам, минуя контейнер (обработка запросов
по умолчанию). Такое решение повышает
гибкость, не понижая при этом эффективности
обработки запросов.
Вложенные в контейнер объекты
могут быть как активными, так и пассивными.
Активные объекты генерируют события, которые,
в виде сообщений, поступают к контейнеру.
Пассивные объекты не создают сообщений,
а лишь выполняют распоряжения, поступающие
от контейнера.
Генерация события активным
объектом не обязывает контейнер обрабатывать
соответствующее сообщение, то есть, данное
сообщение может быть проигнорировано контейнером.
Однако не исключено, что с сообщением
от вложенного объекта будет связана некоторая
схема обработки. И поступление такого
сообщения будет обработано аналогично
обработке внешнего, по отношению контейнеру,
сообщения.
При обработке внутренних
сообщений необходим контроль для исключения
бесконечных циклов, когда вложенный объект
генерирует сообщение, схема обработки
которого предусматривает обращение к этому
же свойству вложенного объекта, которое
может вновь создать то же самое внутреннее
сообщение и т.д. Данный вид контроля должен
выполняться системой автоматически при
создании новой схемы обработки внутреннего
сообщения.
Теоретически контейнер
может непосредственно включать в себя
структуры вложенных объектов. Результат
такого включения семантически близок к
множественному наследованию и, как отмечалось
ранее, такое решение очень неудобно в
практическом применении. Следует отметить,
что физическое вложение структур, если
и возможно то, только для статических
контейнеров. Динамические контейнеры потребуют
очень больших накладных расходов для поддержания
такой организационной схемы. Помимо этого,
прямое включение не позволяет разделять
один и тот же объект между несколькими
контейнерами, что может осложнить реализацию
параллельной обработки в соответствующих
задачах.
Однако и со статическими
контейнерами возникают серьёзные проблемы.
Как отмечалось ранее, при рассмотрении
темы «Полиморфизм», вложенные в статический
контейнер объекты выступают в некоторых
ролях и замена одного объекта на другой
возможна, если новый объект способен выступать
в тех же ролях, в которых выступал его
предшественник. Однако одну и ту же роль
могут играть совершенно разные объекты,
в том числе и объекты, не находящиеся
в связи наследования. Как следствие, структуры
этих объектов могут значительно отличаться
друг от друга, что вызовет определённые
проблемы при замене одного объекта другим.
Включение по ссылке имеет
очевидные преимущества по сравнению с
непосредственным включением вложенных
объектов в контейнер. Но остаётся вопрос
о том, должна ли ссылка быть указателем
на структуру объекта или она должна представлять
собой некоторый идентификатор объекта,
не имеющий прямой связи с местом расположения
объекта в памяти. На первый взгляд, преимущества
использования указателей состоят в большей
эффективности, но здесь уместно вспомнить,
что вложенные объекты инкапсулированы,
и обращение к ним происходит на основе
объявленных интерфейсов. В этом случае
необходимость и эффективность использования
указателей, вызывает сомнения. С другой
стороны, наличие указателей исключает
возможность использования в одном контейнере
объектов, расположенных в различных адресных
пространствах, например, на разных компьютерах.
Это ограничение может создать проблемы
при разработке распределённых систем.
Кроме того, часто отладку кода новых объектов
лучше производить изолировано от остального
кода, в отдельном адресном пространстве.
После того, как процесс отладки и опытной
эксплуатации успешно завершены, новый
объект может размещаться в том же адресном
пространстве, что и другие, вложенные
в этот контейнер, объекты. Процесс перемещения
объекта из одного адресного пространства
должен быть прозрачен не только программиста,
но и для контейнера-владельца.
Идентификация объектов
внутри контейнера без применения указателей
способствует инкапсуляции вложенных объектов,
поскольку от контейнера не требуется знать
ни размеры вложенных объектов, ни их адреса.
В таком случае, вложенные объекты «спрятаны»
от контейнера, который ими владеет. Благодаря
этому становится возможным и использование
несколькими контейнерами одного объекта,
и произвольное размещение вложенных объектов
в различных адресных пространствах, и
высокая защищённость вложенных объектов,
как от контейнера, так и от взаимного
влияния. При этом указатель на объект
является частным случаем идентификатора
объекта, если учесть, что два объекта
не могут располагаться по одному адресу.
Следовательно, система может использовать
указатели, если это необходимо и возможно,
но от разработчиков не требуются выполнения
операций манипуляции с адресами, которые
могут привести к снижению надёжности системы.
В свою очередь, контейнер
«невидим» для вложенных в него объектов.
Вложенные объекты не должны зависеть от
того, в какой контейнер их вложили. Это
аналогично тому, что работоспособность
подпрограммы не должна быть связана с
тем, в какой программе её используют.
|