Последнее обновление:
| Главная |  Статьи |  Назад | 
Технология создания плагинов. Часть 1: теория.

Введение

       Прежде чем перейти к самой технологии непосредственно, следует рассмотреть то, для чего всё это нужно.
       При создании программного продукта всегда необходимо помнить, что требования к программному обеспечению всегда изменяются, вне зависимости от желаний программиста-разработчика. Изменение требований непосредственно отразится на программном коде, который надо будет модифицировать. Модификация кода приводит к появлению нежелательных побочных эффектов (проще говоря, ошибок) в программе. Исходя из этого, необходимо создавать такой код, при модификации которого в будущем количество нежелательных побочных эффектов было бы минимальным. Это достигается совокупностью некоторых подходов при программировании.
       Первым из многих подходов является максимальное разделение обязанностей объекта. Под обязанностями объекта мы будем понимать набор функций (возможностей) объекта, которые он должен реализовать. Например, каждый объект обязан инициализировать себя при вызове конструктора. После создания объекта, то есть вызова его конструктора мы можем быть уверены в том, что дальнейшее использование объекта не вызовет непредсказуемого поведения. Таким образом, каждый объект сам отвечает за своё поведение. В случае нежелательных побочных эффектов, это позволит быстрее обнаружить место неполадки.
       Вторым подходом является инкапсуляция. Инкапсуляцию чаще всего определяют как сокрытие данных. В объектно-ориентированных системах, в C++ в частности, существует несколько уровней доступа к элементам объектов: public (открытый) - доступ разрешён для всех, protected (защищённый) - доступ разрешён только объектам этого же класса или его классам-потомкам, и private (закрытый) - доступ предоставляется только объектам этого класса. Но не следует понимать инкапсуляцию только как сокрытие данных на уровне самих классов, она представляет собой нечто большее. В общем случае понятие инкапсуляции охватывает любой тип сокрытия информации.
       Третьим подходом является наследование. Наследование позволяет группировать схожие по назначению классы через определение их базового класса. Зачастую базовым классом является абстрактный класс. Абстрактные классы часто описываются как классы, не предусматривающие создание своих экземпляров. Это обстоятельство позволяет присвоить собственное название некоторому множеству родственных классов.
       Четвёртым подходом является полиморфизм. Под полиморфизмом мы будем понимать возможность одним и тем же способом обращаться к различным подклассам некоторого родительского класса. При этом достигаемый результат будет зависеть от конкретного типа того класса-потомка, к которому производится обращение. На полиморфизме основывается рассматриваемая технология создания плагинов.

Постановка задачи

       Как уже было написано выше, требования к программному обеспечению постоянно меняются. Зачастую, этими требованиями являются изменение или добавление возможностей программы. Конечно, можно каждый раз при добавлении новых возможностей просто дописать существующий код программы, но со временем, если такие требования постоянно будут предъявляться, код программы заметно увеличится, что значительно усложнит её сопровождение и повысит вероятность нежелательных побочных эффектов. Одним из шаблонных способов решения этой проблемы являются плагины.
       Плагин (Plug-in, взаимозаменяемый модуль) - программно-завершённый модуль, который программа способна распознать и использовать его возможности. Похожей технологией является технология COM, или её частный случай - ActiveX, однако плагины являются более простыми в реализации, т. к. предусматривается реализация для конкретного программного продукта, а, значит, нет необходимости предусматривать дополнительные возможности, которые зачастую бывают сложными в реализации (например, OLE).
       Как правило, на этапе конструирования архитектуры будущего программного продукта, выявляются те существующие возможности программы, которые в последствии могут быть модифицированы или дополнены. Например, программа должна загружать изображение из файла в память. Сразу можно предположить, что эту возможность следует реализовать с помощью технологии плагинов. Тогда в будущем у нас появится возможность всего лишь дописать отдельный модуль программы, а не перекомпилировать весь программный продукт. Кроме того, если после добавления модуля будут возникать нежелательные побочные эффекты, то вероятнее всего, достаточно будет исправить конкретный модуль, выпустив его новую версию, а не искать место неполадки по всему коду приложения. Это возможно за счёт инкапсуляции данных в разных модулях и разделение обязанностей модулей, т. е. каждый модуль сам отвечает за корректность своей работы.

Архитектура

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

Идентификация

       Рассмотрим порядок идентификации плагинов. После загрузки модуля плагина в память, приложение запросит модуль создать объект плагина. В случае ошибки, будет принято решение, что модуль не является плагином программы и модуль будет выгружен. Если же указатель на объект будет получен, то приложение постарается идентифицировать тип плагина. Для этого объект последовательно будет приводиться к каждому типу динамическим кастингом (приведением типов, в C++ это конструкция dynamic_cast) до тех пор, пока это приведение не будет успешным. Как только приведение было успешным, программа добавляет объект в список плагинов соответствующей группы. Фактически, после этого плагин уже готов к работе. Если же объект не удастся привести ни к одному из типов, то будет принято решение, что плагин предназначен для более новой версии программы. Следовательно, поскольку объект не может быть использован, его следует удалить и выгрузить модуль плагина из памяти.
       Динамический кастинг (приведение типов) используется только главным приложением. Что если какому-либо плагину для корректной работы потребуется наличие другого плагина? Плагин должен уметь обратиться к системе с запросом на наличие необходимого плагина. Для запроса потребуется введение дополнительной идентификации плагинов: в строковом или числовом виде. Лучше использовать строковый вид, так как он более удобен для программистов, ведь не стоит забывать, что программируют люди, для которых гораздо более понятен мнемонический текст, нежели непонятное число. Кроме того, строка даёт большее количество вариаций идентификаторов. Но тут появляется новая проблема: дополнительные идентификаторы могут совпасть. Существует множество решений, но наиболее логичных из них только два: либо удалить объекты с повторными идентификаторами, оставив только один объект (либо первый, либо последний), либо спросить у пользователя, какой объект оставить (удалив все остальные объекты со схожими идентификаторами).

Инициализация и завершение работы

       Перед использованием плагина вполне логичной будет процедура инициализации. Эта процедура позволит подготовить плагин к работе. Под подготовкой может пониматься разное, например, это может быть выделение памяти для корректной работы плагина, или, например, может возникнуть ситуация, когда для корректной работы плагина может потребоваться наличие в системе других плагинов. Для этой цели выше мы ввели дополнительную идентификацию. Наличие в системе необходимых плагинов можно будет проверить специальными запросами во время процедуры инициализации. В случае, если по какой-либо причине плагин не сможет начать свою корректную работу, достаточно будет дать отказ системе при инициализации.
       Для обратных по назначению целей служит процедура завершения работы. Во время этой процедуры плагин может выполнить необходимые действия для корректного завершения работы. Например, плагин может очистить использованную им память и/или сохранить свои настройки.
       Как правило, для реализации процедур инициализации и завершения работы достаточно введение двух дополнительных методов в главный базовый класс всех плагинов. Эти методы могут называться, например, Enable и Disable ("включить" и "выключить").

Использование

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

Соглашение об интерфейсах

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

--
Евгений Назаров
nevsoft@mail.ru
Последнее обновление:

| Главная |  Статьи |  Назад | 
Hosted by uCoz