developing.ru - клуб программистов Здесь может быть и ваша реклама.
developing.ru >технология COM >
Михаил Безверхов
aspid@developing.ru

Фабрикант, но не капиталист

Продолжаем наше изложение. Что такое "фабрика класса"? Это понятие приходит из ООП и обозначает механизм, который "производит объекты". Проблема, в которой оно возникает состоит в следующем - до запуска программы у нас есть полный набор статических типов. И нет ни одного их экземпляра. Нам требуется процедура времени исполнения, которая бы из статического типа делала экземпляр, т.е. выполняла бы преобразование "имя статического типа - адрес экземпляра". Эта процедура и называется "фабрика класса", она существует в любой реализации ООП. Но в каждой реализации она существует по-своему, сообразно тому "набору артефактов реализации", который используется в данном средстве. В COM явно говорят об "объекте фабрики класса", в C++ об этом не принято говорить. Но, не существующая в виде отдельной сущности, фабрика класса есть и в C++ - это пара "оператор new - конструктор класса". Поскольку в C++ возможно создание экземпляров класса не только в динамической, но в и статической и автоматической памяти, то точнее бы говорить, что на самом деле это пара "распределитель памяти - конструктор класса".

О фабрике класса не как концепции, а как сущности я (да, наверное, и многие читатели) впервые услышал в COM. Дейл Роджерсон очень усердно и образно рассказывал в своей книге об особом "компоненте производящем объекты", чем, например, меня с толку сбил - я подумал тогда, что раз это "компонент", то он один должен уметь производить объекты всех статических типов, реализуемых данным сервером. При дальнейшем изучении выяснилось, что это не так, но сейчас я вполне понимаю деяние Роджерсона так как сам нахожусь в том же затруднении - фабрика класса принадлежит связке "статический тип - экземпляр", а этой-то связке как раз нет никакого названия. Кроме того, если здесь удерживаться в рамках абстракций C++, возникает и вторая трудность - надо как-то обосновывать такую ересь*, как "объект статического типа", который устойчиво понимается в программистском сообществе как "объект в статической памяти", а вовсе не так, как надо бы... Словом, видимо, с лёгкой руки Роджерсона (а может быть - и кого-то в Microsoft ещё до Роджерсона...) понятие "объект фабрики класса" прижилось не вызывая побочных мыслей - термин есть, как им пользоваться - понятно.

Но, хотя это вполне допустимая интерпретация, я не думаю, что ей дОлжно следовать в систематизированном понимании. Правильнее было бы говорить всё-таки об "объекте статического типа" реализующем особую процедуру - фабрику класса. В пользу того, что такое понимание более системно, можно привести несколько соображений. Во-первых, - фабрик класса у статического типа может быть много (не удивляет же нас, что у класса в C++ полиморфизм распространяется и на конструктор?), а во-вторых о фабрике класса как-то не принято говорить, когда экземпляр "обычного" COM-объекта непосредственно, минуя CLSID, порождает экземпляр COM-объекта другого статического типа. Почему тогда такой COM-объект не называется фабрикой класса? В-третьих, привычность этой интерпретации не означает, что она не вызывает проблем с унифицированным пониманием в другом месте. Например, копаясь на тему class factory в MSDN я обнаружил абзац, достойный, чтобы его процитировать:

"If you've used Java extensively, you can think of the COM class object as being roughly like a Java object of class "Class." And you'll note that Java's Class.newInstance is analogous to IClassFactory::CreateInstance just as COM's CoGetClassObject is analogous to the Class.forName static method"(Если вы широко используете Java, вы можете думать, об объекте класса COM как о грубом подобии класса "Class.". И вы отметите, что в Java Class.newInstance аналогичен IClassFactory::CreateInstance тогда как CoGetClassObject из COM аналогичен статическому методу [Java] Class.forName).

Вон оно что! Оказывается, что это только в COM механизм, создающий из имени типа адрес его экземпляра считается "объектом фабрики класса". В Java, в полном соответствии с ООП, его считают всё-таки "статическим методом". И очень конфузливо оговариваются: "...being roughly like...".

А я не вижу никаких причин, чтобы так нельзя было считать и в COM - во всяком случае COM есть такая же самостоятельная реализация ООП, как и C++ и Java. Всё, что удерживает нас от понимания, что "так оно и есть" - стереотип, что vtbl, экспонирующая IUnknown представляет объект. Ничего подобного! Это - стереотип, приходящий из C++. На самом деле vtbl представляет не объект, а только способ взаимодействия с объектом - интерфейс. Сам объект значительно больше того интерфейса, который экспонирует. И, поскольку в COM интерфейс - единственный способ взаимодействия с COM-объектом, мы волей-неволей вынуждены обо всех категориях мыслить как об объектах с интерфейсами - если у вас в руках только молоток, то и все проблемы сами собой кажутся гвоздями.

Для дальнейшего понятного изложения мне требуется ввести специальный термин. Назовём тот "COM-объект", который мы до сих пор изучали (статьи с Откуда суть пошли интерфейсы программные? по Железный век на смену веку бронзовому) "элементарным COM-объектом". Он состоит из одного или более интерфейсов, которые разделяют одну и только одну реализацию IUnknown, и одной или более областей данных, обслуживаемых этим IUnknown. Всё определение строится вокруг того, что реализация IUnknown у этой сущности - самостоятельная и одна. Не следует считать "элементарный COM-объект" ни "типом", ни "объектом" в смысле ООП, потому что он есть только та программная конструкция, тот примитив, посредством которого в COMреализуется модельный, полезный объект. Когда перед вами встанет задача не определения, а реализации объекта, вы возьмёте этот универсальный примитив и в нём реализуете все те свойства, которые и отличают ваш объект. В определение же свойств объекта модели сам примитив не добавляет ничего.

Теперь помыслим в том направлении, подозрение в котором нам доставила цитата из MSDN - вызов DllGetClassObject есть вызов статического метода. Что делает этот статический метод? Он вызывает к жизни механизм, который может создавать полноценные экземпляры... Как хотите, но это - в чистом виде пара "оператор new - конструктор класса". Ведь оператор new - тоже статический метод, а то, что механизм этот "завёрнут" в конструкцию "элементарного COM-объекта" - артефакт реализации COM, просто в COM никакого другого способа взаимодействия нет. И интерфейс, который мы при этом получаем, вполне можно считать аналогом конструктора объекта. И нам никто не запрещает иметь полиморфный конструктор - несколько таких интерфейсов, каждый из которых делает экземпляры объекта каким-то отличным от других способом.

Итак, всё встало на свои места. При вызове DllGetClassObject мы передаём ей CLSID статического типа и получаем в ответ ссылку на элементарный COM-объект, который реализует статические аспекты этого типа. Если наш COM-тип уподоблять классу C++, то посредством этого объекта мы получаем доступ к статическим методам класса - для доступа к статическим методам класса не нужно иметь никакого экземпляра, достаточно при вызове назвать имя класса. И мы - пока ещё кроме CLSID, именующего тип, тоже ничего не назвали. Далее, мы можем вызвать статический метод названного класса, который "отведёт память - вызовет конструктор - создаст экземпляр" и вернёт нам адрес экземпляра - сущности, реализующей динамические аспекты этого типа. Конечно, поскольку у нас COM-, а не C++-объект, вернёт он ссылку на другой элементарный COM-объект, но ведь это к философии взаимодействия в ООП отношения не имеет - это только артефакт данной реализации, которая отличается от реализации C++.

Но, коль скоро мы и действительно имеем дело с сущностью, представляющей все статические аспекты типа, почему мы в наборе её статических методов ограничены только конструкторами? Что мешает реализовать в её составе интерфейс, который не будет выполнять "создание экземпляра", а будет выполнять какие-то другие действия, относимые ко всему типу сразу? Так - обычно не делают, но ведь "не делают" не означает, что "невозможно сделать"? Значит у нас имеет место быть не какая-то особая внесистемная сущность "фабрика класса", а сущность, представляющая все статические аспекты типа. В том числе - реализующая и фабрики класса. Эта сущность является элементарным COM-объектом, и она - отличается от сущностей, которые представляют собой динамические аспекты (экземпляры) типа - те тоже являются элементарными COM-объектами, но - другими, физически с первой сущностью никак не связанными. А всё вместе - "статический тип COM-объекта", именуемый одним CLSID.

Так почему из всех статических аспектов типа в COM и реализуются только фабрики класса? Почему возможность реализовать иные "статические методы типа" в COM фактически не используется? Ответ прост - потому, что хотя бы раз без фабрики класса обойтись никак нельзя, а вот без статических методов можно. Т.е. можно реализовать их фактическую функциональность без них. Ведь то, что вы имеете дело с "COM-объектом" или "статическим типом COM" - абстракция, или, если хотите - иллюзия клиента. Это клиент, феноменологически наблюдая за той сущностью сервера, с которой он взаимодействует посредством интерфейсов, может заключить, что она имеет все признаки объекта - идентичность, состояние и поведение. А также и отличается ими от других таких же - имеет и признаки статического типа. Но внутри сервера всё это реализуется отнюдь не одним классом C++ и не одним его экземпляром! Внутри сервера может иметь место очень сложная модель в которой несколько "СOM-типов" разделяют одни данные, так что параметр, измененный у COM-объекта X может немедленно изменить состояние COM-объекта Y, который внешне - с точки зрения клиента - никак с X не связан. Да и COM-сервер может быть реализован не только на языке объектно-ориентированного программирования, но и на процедурном языке - на C, например. Клиент этого никогда не узнает - его "представления о сервере" и ограничиваются "сущностью с интерфейсами", которая ведёт себя так, как положено объекту.

Поэтому то решение, которое в C++ экономит код, в данном случае имеет выигрыш только в "чистоте идеи", но не в удобстве реализации. Это лишний раз должно наводить читателя на мысль - "объект" есть модельное понятие, позволяющее нам с меньшими собственными усилиями строить модели окружающей реальности и воплощать их в виде программ. Но было бы очень большой ошибкой говорить об "объектах программы" не упоминая уровня абстракций, на каком эти объекты рассматриваются. COM это очень прекрасно иллюстрирует - то, что клиент COM-сервера считает "объектом" внутри сервера реализуется чем и как угодно, в том числе, но не обязательно, - и "объектами" меньшего уровня. Можно привести и другой пример такого же "скачка" - "объектные конструкции" C++ транслируются компилятором в плоский процедурный код, работа которого, тем не менее оставляет нас в убеждении, что перед нами - взаимодействие объектов.

В этом, кстати и причина моего упорного нежелания употреблять термин "класс", когда речь идет о COM - ведь "статический тип" это - действительно статический тип. Т.е. самая общая, без какой-либо детализации, абстракция, максимально инкапсулированно обозначающая состояние и поведение. А никакой не "класс", упоминание которого тянет за собой стереотипы "инкапсуляции", "иерархии наследования","полиморфизма" которые - как категории - в COM сполна присутствуют, но которые мало похожи на те артефакты, которые являют нам C++ и Java. Можно только догадываться о причинах, почему в англоязычной литературе статический тип COM был назван "класс COM" и его идентификатор называется CLSID. Ясности это не добавило. Посмотрите в MSDN - везде, где приходится работать сразу на двух уровнях - уровне абстракций собственно COM и уровне абстракций его реализации на C++ чуть ли не половину статьи занимает постоянное отмежёвывание, что вот этот "класс" совсем не "тот класс", а вот на самом деле является вот такой конструкцией из других..."классов". Посмотрите хотя бы на приведённую мной цитату из MSDN и употребление в ней слова "class". А ведь в русском, против английского, даже нецензурных слов обнаруживается больше, для каждого случая - свой, точный, термин! Так стоит ли слепо копировать заграницу даже в её ошибках и немощах?

Тем не менее, если вам всё-таки требуется сущность аналогичная статическому методу C++ вы вполне можете её реализовать - COM этого не запрещает. Он, правда, этому и не способствует - в силу "крупноблочности строительства" в COM очень редко возникает надобность именно в таком решении, а клиенты на скриптовых языках вообще не умеют работать с "типом", а только с "экземпляром" COM-объекта.

Интерфейсов фабрики класса в COM стандартно известно два - IClassFactory и IClassFactory2. Их мы рассмотрим в следующих статьях. Возможно также создание COM-объекта "не путём использования фабрики класса", т.е. конечно, с использованием той же концепции, но без использования интерфейса, который стереотипно так называется. Возможно написание собственной фабрики класса - интерфейса, который как-то совершенно особенно будет создавать вам экземпляры объектов, именно так, как вам надо. Никакой ошибкой не является и создание экземпляров так, как мы до сих пор делали в примерах кода в нашей серии - ну не было у наших типов реализации статических аспектов и не было. Тем не менее, стандартные интерфейсы - это "стандартные клиенты", которые, в частности - Visual Basic, Delphi, FoxPro, J++, клиенты, создаваемые MFC и ATL - пользуются именно стандартными интерфейсами фабрики класса. И если вы намереваетесь сделать "стандартно совместимый статический тип" - вы обязаны в числе прочих фабрик класса реализовать и такой интерфейс. Он может быть и единственным из всех фабрик класса - этого минимально достаточно.

Надо также ответить и на вопрос - а что даёт, кроме дополнительного шага в создании экземпляра, использование концепции "фабрики класса"? Даёт оно, в общем, то же, что даёт использование перегруженного конструктора - возможность по разному (а не однотипно, как это было до сих пор в наших примерах) создавать экземпляры объектов. Ведь CLSID - один, но это не означает, что процедура создания экземпляра объекта тоже возможна только одна. Вообразите, например, что у вас есть реализация обработчика файла. Обработка производится так - вы "напускаете" обработчик на файл, а он организует вам небольшое "окно" позволяющее доступ к содержимому этого файла. И экземпляр объекта - как раз и представляет для вас это самое "окно". Ситуация, когда вы именно экземпляру указываете имя файла порождает вам всегда "одно окно на один файл". А если вам может потребоваться несколько окон к одному файлу? Указывая имя файла не "объекту экземпляра", а "объекту типа" вы несколько меняете ситуацию - одна и та же реализация теперь сможет организовать сколько угодно окон к одному файлу и сколько угодно разных файлов тоже смогут ей обслуживаться одновременно. Это полностью аналогично использованию в C++ конструктора копирования, например.

Эта особенность используется в MTS - контекст транзакции в нём определяется именно фабрикой класса, но я должен заметить, что приёмы программирования в C++ и в COM - отличаются. Если в C++ принято нагружение конструкторов разной полезной функциональностью, иногда даже до такой степени, что вся программа большей частью сводится к порождению объектов разными способами, то для COM такой подход нехарактерен. В COM, обычно, используется только "конструктор по умолчанию", а вся функциональность по изменению внутреннего состояния объекта реализуется методами.


* Это - точное употребление слова! В книге "Молот Ведьм", написанной инквизиторами Шпренгером и Инститорисом в XV веке говорится - "...еретиком называется всякий вообще, кто "либо рождает ложные или новые мысли, либо следует им" (falsas vel novas opiniones gignit vel sequitur). А в то время гораздо лучше разбирались в этом предмете!

предыдущий выпускархив и оглавлениеследующий выпуск
Авторские права © 2001 - 2004, Михаил Безверхов
Публикация требует разрешения автора

разделы сайта

форум

технология COM

оптимизация сайтов


© 2000-2004 Клуб программистов developing.ru