Обзор
В отличие от других отраслей, разработка программного обеспечения требует, чтобы прикладные модули были скомпилированы и слинкованы с другими зависимыми частями приложения. Всякий раз, когда разработчик хочет использовать в приложении другую логику или новые возможности, для того, чтобы эти изменения вступили в силу, ему или ей необходимо модифицировать и перекомпилировать первичное приложение.
В производстве такое ограничение недопустимо. Можете ли вы себе представить, что вам пришлось бы переделывать автомобильный двигатель, если бы вы захотели заменить ваши шины от изготовителя на более совершенные? Это могло бы пролиться золотым дождем на механиков, но чрезмерные эксплуатационные издержки приведут к уменьшению спроса на автомобили, а от этого пострадают все: потребители, производители автомобилей, те же механики. Фактически, одним из основных факторов успеха промышленной революции стала способность взаимозаменяемости деталей машин, т.е. использование компонентов. Сегодня мы не задумываясь заменяем компоненты и добавляем новые принадлежности в наши автомобили.
Автомобили ничего "не знают" о шинах, которые они используют. Шины имеют свойства (ширина колеса и пр.). Если свойства у различных шин совпадают, то эти шины взаимозаменяемы. Светильник ничего "не знает" о лампах, которые в нем используются. Если параметры ламп (диаметр завинчивающейся части) удовлетворяют требованиям изготовителя осветительного прибора, то эти лампы взаимозаменяемы. Давно ли индустрия программного обеспечения стала догонять остальную часть мира и строить компоненты, которые понятия не имеют о том, как они будут использоваться ? Для отрасли, которая считается передовой, мы действительно плетемся в хвосте.
На первый взгляд, динамически подключаемые библиотеки (DLL) обеспечивают решение указанных выше проблем. Следующая выдуманная история покажет, что это не так.
Предположим, вам нужно разработать приложение для компании Acme Gas Tanks. Приложение будет показывать уровень бензина в новом престижном топливном баке Acme на 1000 галлонов.
Во-первых, вы создаете индикатор уровня на основе ActiveX(tm), который имеет три отметки: текущий уровень топлива в баке, минимально возможный безопасный уровень и максимально возможный безопасный уровень. Вы пишете DLL, назвав ее GasTankLevelGetterDLL, которая имеет следующие функции:
- long GetLowestPossibleSafeLevel();
- long GetHighestPossibleSafeLevel ();
- long GetCurrentLevel();
Пару недель спустя, мистер Ричи Рич ( Richy Rich ) вызывает вас к себе и сообщает, что ваш ActiveX для индикации уровня является самой красивой вещью, которую он когда-либо видeл в своей жизни. Ричи говорит вам, что хочет использовать его для контроля уровня в своем аквариуме на 5000 галлонов. Он заявляет, что индикатор должен показывать те же три уровня, что и для топливного бака. Вы говорите ему, что зайдете к нему завтра, а пока подумаете над его предложением.
На следующий день вы приходите к мысли, называть все DLL, которые реализуют те самые три функции, хотя и с различной внутренней обработкой, одинаково - LevelGetterDLL. Проблема контроля уровня воды в аквариуме мистера Ричи решена. Он проверяет ваше приложение 24 часа в сутки, чтобы убедиться, что его рыбки находятся в полной безопасности. Вы также передаете новую версию LevelGetterDLL Acme. Другие компании связываются с вами на предмет использования вашего ActiveX индикатора уровня. Вы отвечаете им: "Нет проблем! Возьмите эти три функции, назовите вашу DLL LevelGetterDLL, и все готово." Вам необходимо всего лишь один раз перекомпилировать ваше приложение, чтобы оно поддерживало новую версию LevelGetterDLL, но поскольку во всем мире все называют свои DLL одинаково (LevelGetterDLL) и используют одинаковые неизменные три метода, то все работает превосходно, и вам никогда не придется перекомпилировать ваше приложение снова.
Вы возвращаетесь домой, чувствуя себя немножко гением.
На следующий день, открыв The Wall Street Journal , вы обнаруживаете, что Ричи Рич разбился на своем вертолете. По дороге в штаб-квартиру Rich Inc. ему не хватило топлива. Похоже, Ричи был клиентом Acme и запускал оба приложения на своем компьютере одновременно. Приложение 1 было то самое, которое вы разработали с использованием LevelGetterDLL для контроля уровня в его аквариуме. Приложение 2 было сделано по заказу Acme для контроля уровня топлива, в нем использовалась та же версия LevelGetterDLL, которая была установлена на вертолете Ричи. И хотя Ричи запускал оба приложения, Приложение 2 для топливных баков Acme использовало DLL LevelGetterDLL для аквариума и показывало уровни 5000-галлонного аквариума вместо 1000-галлонного топливного бака, поскольку версия для аквариума была установлена на компьютер последней. Ричи ничего не знал о том, что его вертолету не хватит топлива. Rich Inc. подает в суд на Acme, которая, в свою очередь, подает в суд на вас. Другие компании, которым вы посоветовали ваше решение, также подают на вас в суд. Если бы вы использовали Component Object Model (COM), Ричи Рич был бы жив, и вам не пришлось бы садиться на скамью подсудимых.
Правило Если две или более DLL предоставляют одинаковые функции (immutability), вы можете использовать любую из этих DLL. Однако одно приложение не может использовать сразу несколько DLL, как и не могут одновременно несколько таких DLL находиться на одном и том же компьютере. Технология COM решает эту проблему. Два сервера COM с идентичными интерфейсами (и следовательно методами) могут использоваться двумя различными приложениями и могут находиться на одном и том же компьютере, поскольку они имеют различные идентификаторы CLSID, и, следовательно, различны на бинарном уровне. Кроме того, технически эти два сервера COM взаимозаменяемы.
Отсутствие "взаимозаменяемых деталей" (компонентов) присуще индустрии программных разработок в силу ее относительно молодого возраста.
Однако, подобно индустриальной революции, создавшей независимые детали машин, технология COM реализует это через программные компоненты. Понимая смысл CLSID и неизменности интерфейсов, можно написать законченный plug-in без какого-либо знания о клиенте. Это означает, что Приложение 1 может использовать или Plug-In1 или Plug-In2. Еще лучше, чтобы Приложение 1 могло динамически переключать Plug-In1 и Plug-In2. Проектирование приложений, использующих динамически заменяемые вставки (plug-ins) сделает для программной индустрии то же самое, что сделали детали машин и механизмов для промышленной революции.
Восторгаясь Active Template Library (ATL) и Distributed COM (DCOM), мы постепенно забываем, что лежало в основе появления COM. Способность DCOM использовать удаленный вызов процедур (remote procedure calls, RPC) выстраивать данные (marshaling) воодушевляет ( и, возможно, является одной из причин роста популярности COM за последние 12 месяцев), однако это не главное, почему была разработана технология COM. Главное, ради чего создавалась COM, - предоставить производителям программ возможность встраивать новые функциональные части в существующие приложения без перестраивания этих приложений. Компоненты COM должны быть спроектированы как взаимозаменяемые вставки (plug-ins), независимо от того, является ли компонент COM локально подключаемой DLL или удаленно запускаемым сервером.