Письмо 03 - Контейнеры

Контейнеры

Существует два основных вида контейнеров: статический и динамический контейнера, хотя вполне допустима и их комбинация. Отличие видов состоит в том, как контейнеры работают с вложенными объектами. Если обращения контейнера к вложенным объектам происходит только через общий интерфейс (все вложенные объекты выступают в одной роли), то такой контейнер является динамическим. Статический контейнер может обращаться к любым свойствам вложенных объектов.

Статический контейнер характеризуется уникальностью роли каждого вложенного объекта. В классе «автомобиль» роль каждого вложенного объекта «кузов», «двигатель», «трансмиссия» и «шасси» является уникальной. Следовательно, автомобиль – это статический контейнер. В статическом контейнере нельзя произвольно добавлять новые или удалять существующие объекты. Это связано с тем, что уникальность роли объекта предполагает его жёсткую привязку к схемам обработки сообщений, приходящих к контейнеру. При изменении количества или типов вложенных объектов необходимо переопределить свойства контейнера, поскольку они реализуются через интерфейсы вложенных объектов. Возвращаясь к примеру с автомобилем, можно сказать, что изменение числа вложенных классов в статическом контейнере равносильно удалению колёс во время движения автомобиля. Сказанное не означает, что единожды созданный статический контейнер не может быть изменён впоследствии. Изменения возможны и допустимы, но не в рамках исполняемой задачи.

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

Обычно основу динамического контейнера составляют объекты одного класса, это строгое, но не обязательное требование. В качестве примера динамического контейнера можно рассмотреть массив, стек, очередь, дек или список. Особенностью динамического контейнера является возможность произвольного изменения количества вложенных объектов. Это обусловлено тем, что обращение к объектам строится исключительно на основе роли, единой для всех объектов. Например, если нам необходимо сравнивать объекты с некоторым значением определённого типа и копировать их в заданную область памяти, то этими свойствами должны обладать все вложенные в динамический контейнер объекты, независимо от того, какому классу они принадлежат. Именно эта особенность позволяет произвольно подключать к контейнеру новые объекты. Схема работы динамического контейнера задаётся на стадии его проектирования. Допустимы, например, схемы, когда сообщение, поступившее к контейнеру, просто перенаправляется на один или более объектов, которые в данный момент являются активными. Но возможно, что сообщение проецируется на все объекты или часть объектов динамического контейнера.

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

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

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

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

Второе качество контейнера, заслуживающее отдельного рассмотрения, заключается в том, что его можно конструировать, а не кодировать. Свойства контейнера реализуются посредством схем. Схемы представляет собой граф разложения входного сообщения на сообщения к вложенным объектам. Таким образом, любой контейнер представляет собой менеджер запросов к вложенным объектам. В свою очередь, конструирование предполагает если не полное отсутствие кодирования, то, по крайней мере, высокий уровень инструментальных средств и простоту операций. Можно констатировать, что создание кода свойств элементарных классов – это практически единственная область программирования в том виде, как мы привыкли его видеть.

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

Контейнер, в отличие от элементарного класса, обладает ещё одной особенностью. Как и любой другой класс, контейнер поддерживает инкапсуляцию. Но инкапсуляция распространяется только на сам контейнер, на его структуру данных и его методы. Несколько иначе обстоит дело с вложенными объектами. Поскольку контейнер является сборной конструкцией, то это означает, что мы можем различать его составные части. То есть, вложенные объекты не инкапсулируются в контейнер автоматически. Реализация контейнера, как составной сущности, может быть как скрытой (инкапсулированной), так и открытой. Но даже в том случае, если структура вложенных объектов известна внешнему программному обеспечению, доступ к вложенным объектам должен происходить через интерфейс, предоставляемый контейнером. Прямой доступ к вложенным объектам, минуя контейнер, должен быть закрыт, в противном случае трудно гарантировать правильную работу контейнера. Как отмечалось выше, динамический контейнер позволяет добавлять вложенные объекты и удалять их непосредственно во время работы. Статический контейнер тоже может изменять количество вложенных объектов или заменять одни объекты на другие, способные играть те же роли. Однако изменение структуры вложенных объектов у статического контейнера нельзя производить во время работы, поскольку это может привести к сбою в работе контейнера. Более правильным подходом в данном случае будет следующая последовательность действий:

  • выведение статического контейнера из рабочего режима;
  • проведение изменений;
  • повторный запуск в работу.

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

К сожалению, иногда исследователи объектной технологии противопоставляют контейнеры самой объектной технологии. Об этом достаточно много и подробно говорится в книге Тимоти Бадда. Например, он пишет, что «Одна из наиболее необычных идей в STL [Standard Template Library] – обобщённые алгоритмы. Она заслуживает отдельного рассмотрения, поскольку выглядит вызовом объектно-ориентированным принципам, которые мы обсуждали до сих пор…». Т. Бадд исходит из того, что контейнеры обобщают алгоритмы реализации, абстрагируясь от типов объектов, которые они обслуживают. То есть, например, можно создать класс-контейнер, как список или очередь произвольных объектов. Алгоритмы работы со списком или очередью не зависят от того, объекты каких классов вложены в данный контейнер. На самом деле и список, и очередь в данном случае представляют собой частную разновидность динамического контейнера. По всей видимости, есть определённая недооценка или непонимание того факта, что контейнер представляет собой иную сущность, отличную от вложенных в него объектов, даже в том случае, если и контейнер и вложенные объекты имеют один и тот же набор интерфейсов. Организация способа хранения вложенных объектов и взаимодействия с ними менее существенны для прикладной задачи, нежели логика работы контейнера. Поэтому вопросы организации контейнера, способов хранения и взаимодействия с вложенными объектами правильнее оставить за системой, в то время как вопросы описания логики контейнера должны решаться программистом. При работе с динамическими контейнерами, наверное, можно допустить опциональную возможность для того, чтобы программист указал желательный способ организации контейнера, исходя из объёма информации, обслуживаемой контейнером, и/или специфики решаемой задачи.

Сайт Alexus Software Development