Опыт шаблонного проектирования программных продуктов

Удачность внутренней структуры программного продукта, если оставить в стороне его сложность, напрямую зависит от опытности создавших ее людей. То же можно сказать и о качестве программного кода; но если стиль программирования является сугубо индивидуальным, то в области структурных решений опытные разработчики довольно часто приходят к схожим решениям.

Для таких решений характерны:

Попытки найти и систематизировать наиболее удачные решения велись давно, и не только в области разработки программного обеспечения. В 70-х годах XX века архитектор Кристофер Александер (Christopher Alexander) написал серию книг, посвященных проектированию архитектурных сооружений. Одним из центральных в его теории было понятие шаблона.

Из сферы архитектуры этот термин перешел в проектирование программных продуктов, фактически дав имя тем гибким, эффективным и универсальным решениям, о которых шла речь выше. Кроме того, использование данного понятия в новой области добавило еще одно требование, необходимое для отнесения программного решения в разряд шаблонов,? требование "безымянного качества", несмотря на всю его расплывчатость. Структура шаблона не должна производить впечатления искусственности или избыточности; более того, его изучение и применение способно доставлять эстетическое удовольствие - как красиво и правильно построенный дом.

В то время еще отсутствовала четкая систематизация существующих шаблонов и перечень предъявляемых к ним требований, поэтому каждый разработчик использовал собственные конструкции, имена и обозначения. Такая систематизация появилась только в 1995 году, с выходом книги "Design Patterns: Elements of Reusable Object-Oriented Software" (в дальнейшем - просто "Design patterns" или "DP"). Это основополагающая книга по шаблонам проектирования и самая известная из всех изданных по этой тематике. Ее авторов часто называют "бандой четырех" ("Gang of Four", "GoF"). В своей книге они не только привели подробное описание 23-х шаблонов, но и дали формальное определение термину "шаблон проектирования" в сфере разработки программного обеспечения.

Согласно этому определению, шаблон проектирования представляет собой "описание взаимодействия объектов и классов, адаптированных для решения общей задачи проектирования в конкретном контексте" DP. Иными словами, шаблон - это описание структуры классов, которая может быть использована для решения задач определенного рода. Характерными чертами такого решения являются эффективность и гибкость.

Шаблон проектирования, согласно DP, состоит из четырех основных элементов:

  1. Имя - название шаблона, позволяющее проектировать на более высоком уровне абстракции. Ссылкой на имя можно заменяет описание трех остальных элементов.
  2. Задача - описание того, когда следует применять шаблон.
  3. Решение - описание элементов дизайна, отношений между ними, и функций каждого элемента.
  4. Результат - следствия применения шаблона. В результат входит, например, влияние применения шаблона на степень гибкости, расширяемости и переносимости системы.

В DP было включено описание 23-х базовых шаблонов. По назначению они объединяются в группы:

ГруппаШаблоны
Создающие шаблоны (Creational patterns) Фабричный метод, Абстрактная фабрика, Одиночка, Прототип, Строитель
Структурные шаблоны (Structural patterns) Адаптер, Декоратор, Заместитель, Компоновщик, Мост, Приспособленец, Фасад
Шаблоны поведения (Behavioural patterns) Интерпретатор, Шаблонный метод, Итератор, Команда, Наблюдатель, Посетитель, Посредник, Состояние, Стратегия, Хранитель, Цепочка обязанностей

Краткое описание шаблонов:

Мост (Bridge) - отделяет абстракцию от реализации, благодаря чему появляется возможность независимо изменять то и другое.

Итератор (Iterator) - дает возможно последовательно обойти все элементы составного объекта, не раскрывая его внутреннего представления.

Команда (Command) - инкапсулирует запрос в виде объекта, позволяя параметризовывать клиентов типом запроса, устанавливать очередность запросов, протоколировать их и поддерживать отмену выполнения операций.

Наблюдатель (Observer) - определяет между объектами зависимость типа один-ко-многим, так что при изменении состояния одного объекта все зависящие от него получают уведомление и автоматически обновляются.

Абстрактная фабрика (Abstract factory) - создает один или несколько объектов, принадлежащих заданным семействам.

Строитель (Builder) - отделяет конструирование сложного объекта от его представления, позволяя использовать один и тот же процесс конструирования для создания различных представлений.

Компоновщик (Composite) - группирует объекты в древовидные структуры для представления иерархий типа "часть-целое". Позволяет клиентам работать с единичными объектами так же, как с группами объектов.

Интерпретатор (Interpreter) - для заданного языка определяет представление его грамматики, а также интерпретатор предложений языка, использующий это представление.

Посетитель (Visitor) - представляет операцию, которую нужно выполнить над элементами объекта. Позволяет определить новую операцию, не меняя не меняя классы элементов, к которым она применяется.

Прототип (Prototype) - описывает виды создаваемых объектов с помощью прототипа и создает новые путем его копирования.

Заместитель (Proxy) - подменяет другой объект для контроля доступа к нему.

Приспособленец (Flyweight) - использует разделение для эффективной поддержки большого числа мелких объектов.

Стратегия (Strategy) - определяет семейство алгоритмов, инкапсулирует их и делает взаимозаменяемыми.

Фабричный метод (Factory method) - предоставляет интерфейс для создания объекта, реализуя само создание в производных классах.

Одиночка (Singleton) - гарантирует создание не более чем одного экземпляра класса и предоставляет глобальный доступ к этому экземпляру.

Адаптер (Adapter) - преобразует интерфейс класса к нужному клиенту виду.

Фасад (Facade) - предоставляет объединенный доступ к набору интерфейсов подсистемы.

Состояние (State) - позволяет объекту изменять свое поведение при смене внутреннего состояния.

Декоратор (Decorator) - позволяет динамически добавлять объекту функциональность.

Шаблонный метод (Template method) - определяет структуру алгоритма, возлагая обязанности по выполнению некоторых его шагов на подклассы.

Цепочка обязанностей (Chain of responsibility) - отделяет отправителя запроса от получателя, позволяя нескольким объектам обработать запрос.

Посредник (Mediator) - определяет объект, хранящий информацию о способах взаимодействия объектов.

Хранитель (Memento) - позволяет, не нарушая инкапсуляции, получить и сохранить во внешней памяти внутреннее состояние объекта.

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

Библиотека универсального доступа к файлам

Основное назначение данного программного продукта - обеспечение единообразного доступа к файлам, расположенным на локальном компьютере или на http-сервере (точнее говоря, доступ по протоколам file и http). Отсюда очевидна необходимость использования двух разных алгоритмов обращения к файлу; кроме того, к библиотеке предъявляются требования гибкости и расширяемости, так что добавление новых алгоритмов или замена существующих должны осуществляться как можно проще.

Реализованное решение можно назвать "фабрикой". Причиной выбора такого названия служит близость данного решения к шаблонам "Фабричный метод" и "Абстрактная фабрика". Однако реализация этих шаблонов обычно предполагает наличие задающего интерфейс абстрактного класса и его потомков, реализующих механизмы принятия решения о классе создаваемого объекта или группы объектов, в то время как включенный в состав библиотеки класс не имеет чисто виртуальных методов. Библиотечная фабрика содержит алгоритм принятия решения, используемый "по умолчанию" и определенный в виртуальном методе; для создания нового алгоритма, таким образом, нужно создать потомок фабрики, в котором этот метод будет переопределен.

Такое решение, включающее реализацию "по умолчанию", не входит в состав базовых 23 шаблонов, описанных в (хотя его и можно считать модификацией шаблона "Абстрактная фабрика"), но тем не менее довольно распространено и получило условное название "Фабрика классов" ("Class factory"). Условно оно потому, что "фабрикой классов", в зависимости от источника, называют самые разные конструкции: каждую из реализаций базового класса в шаблоне "Абстрактная фабрика" и даже сам этот шаблон; решение, которое заменяет экземпляр класса его интерфейсом, а создающий класс помещает в класс-"одиночку" (так поступает используемая в C#.NET фабрика классов); модель, в которой нужные объекты создаются статическим методом их же базового класса. Сказывается отсутствие общепризнанного систематизирующего ресурса, подобного DP.

На рисунке изображена упрощенная диаграмма классов библиотеки. Их назначение:

Система унифицированного управления приложениями

В число требований к данной системе входят наличие не менее двух типов контролируемых приложений (обычные исполнимые файлы и сервисы WinNT-подобных систем) с единым интерфейсом управления (гибкость и расширяемость можно не упоминать - они если не обязательны, то, во всяком случае, желательны для информационных систем вообще и для всех описываемых в частности). Эти требования, если отвлечься от назначения приложения, практически совпадают с требованиями к библиотеке работы с файлами, описанной в предыдущем разделе. Сходство требований позволяет предположить возможность использования схожих решений: создания базового абстрактного класса, содержащего набор зависящих от конкретного типа приложения операций, и нескольких его потомков, соответствующих возможным типам приложений и по-разному эти операции реализующих.

При реализации был создан управляющий класс, отвечающий одному из дополнительных требований: система должна работать как сервис или как обычное консольное приложение, в зависимости от выбора пользователя. Разработанный ранее проект позволяет легко создавать такие программы, но требует глобального доступа к работающему экземпляру контролирующего приложения класса. Обеспечить такой доступ позволяет шаблон "Одиночка".

Следующий рисунок иллюстрирует итоговую структуру системы:

Назначение классов на рисунке:

Интерфейс работы с системой распознавания речи SpeechPearl

Проектируемая система должна обеспечивать простой и удобный доступ к ресурсам распознавания речи, предоставляемым продуктом SpeechPearl. Необходимость ее создания обусловлена сложностью использования этих ресурсов - собственный программный интерфейс SpeechPearl включает в себя несколько десятков функций. Полное программное руководство по ним занимает более сотни страниц, не считая примеров (среди которых нет ни одного, иллюстрирующего работу в режиме "клиент-сервер").

Еще одна причина разработки интерфейса - в наличии среди стандартных функций SpeechPearl синхронных функций. Синхронные функции на время своего выполнения блокируют работу программы и при интеграции в существующие системы могут снизить их производительность. Таким образом, нужно преобразовать те синхронные функции, выполнение которых может занять значительный промежуток времени, в асинхронные - функции, которые выполняются независимо от основной программы и лишь сообщают ей о своем завершении.

Упростить для пользователя процесс распознавания речи позволяет применение шаблона "Фасад", скрывающего сложное внутреннее устройство системы за простым интерфейсом.

Изменение типа функций требует более сложного решения. В результате анализа были определены три функции, которые вызываются неоднократно и требуют довольно значительного времени для выполнения. Все они различаются по назначению и набору входных и выходных параметров; кроме того, не исключена возможность добавления новых функций при расширении функциональности системы - например, для поддержки режима обучения. С учетом всего вышеперечисленного разумным представляется помещение вызова каждой функции в отдельный класс и наделение этих классов одинаковым интерфейсом. Для конкретной реализации этого решения был применен шаблон проектирования "Шаблонный метод" ("Template method").

Еще один класс предназначен для хранения данных, к которым необходимо обеспечить глобальный доступ. Последний, как и в предыдущем решении, обеспечивает использование шаблона "Одиночка".

Структура системы в целом представлена на следующем рисунке:

Базовые классы:

Отдельно изображена система SpeechPearl с ее собственным программным интерфейсом.

Система управления телефонными звонками

К числу важнейших характеристик при проектировании продукта были отнесены минимальная зависимость (а в идеале - полная независимость) от типа используемых телефонных плат (на тот момент - "Dialogic" или "Ольха") и протоколов передачи данных. В результате была разпаботана структура классов, обеспечивающих эту независимость, и, для тестовых целей, конкретная реализация для программного продукта Dialogic HMP.

В реализации управляющих классов активно используются шаблоны "Одиночка". Они контролируют телефонные каналы, голосовые и медиаресурсы.

Контроль работы телефонного канала обеспечивает набор классов, описывающих машину состояний. В реализации последней были использованы такие шаблоны, как "Состояние" и "Прототип": первый позволяет менять поведение динамически, а второй - создавать экземпляр объекта, класс которого заранее неизвестен. Механизм связи вспомогательных ресурсов с телефонным каналом включает в себя реализацию шаблона "Шаблонный метод".

Основные классы системы:

Дополнительные замечания

Теперь, когда некоторое представление о сущности и применении шаблонов проектирования дано, стоит сделать несколько замечаний:

  1. Большая часть существующих шаблонов проектирования предназначена для использования с языками программирования, основанными на объектно-ориентированной парадигме. Однако существуют и шаблоны, охватывающие остальные языки (хотя их число значительно меньше числа шаблонов, которые уже созданы и классифицированы в объектно-ориентированных языках), а также шаблонов анализа, тестирования, документирования и даже создания музыкальных композиций. Не стоит также забывать о том, что шаблонное проектирование пришло в сферу разработки программного обеспечения из архитектуры.
  2. В том, что касается предметной области приложения, ограничений на применение шаблонов практически не существует. Тем не менее в некоторых специфических видах приложений разработано относительно малое число шаблонов; это касается в первую очередь систем реального времени и встраиваемых систем.
  3. Шаблоны описывают конструкции довольно низкого уровня, используя в качестве элементов классы и объекты. Обычно этого достаточно, но при проектировании больших систем может возникнуть необходимость в использовании самих шаблонов как строительных блоков, скрывающих детали внутренней структуры. Такого подхода придерживается технология POAD ("Pattern-Oriented Analysis and Design").
  4. Шаблоны – это не панацея. Нельзя ожидать, что применение шаблонов решит все проблемы разработки и развития программного обеспечения. "…Шаблоны вообще ничего не гарантируют. Можно говорить только о вероятности получения неких преимуществ. Шаблоны никоим образом не могут заменить человека в творческом процессе". Чем опытнее разработчик и чем лучше он представляет себе возможные направления развития системы, тем выше вероятность реализации предоставляемых шаблонами преимуществ. Несмотря на это, сам процесс проектирования и его последствия лежат целиком на совести разработчика.