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

Так "под флагом" или "под знаменем"?

В предыдущей статье мы рассмотрели очень важное философское обстоятельство, тщательно скрываемое от "непосвящённых глаз" даже такими средствами, как ATL и MFC, не говоря уже о VB и Delphi. Но от глаз "истинного программиста" оно укрываться не должно. Его непонимание обходится дорого, его ясное понимание дорогого стоит.

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

Во-вторых, и об этом я тоже писал ранее, серия статей - не справочник и не приложение к MSDN. Её автор исходит из чрезвычайно глубокого убеждения, сформированного всей его предшествующей инженерской жизнью и практикой - важно понимать суть и философию предмета, а остальное можно и самому откопать, если понадобится. Но копать, не понимая сути и зная лишь отдельные детали, - бесполезно. Есть ли разница в словах, которыми выражается суть? Я думаю, что если и есть,то лишь с точки зрения доходчивости. Если и я и Гради Буч говорим об одном и том же, то в сути мы, безусловно, должны совпасть. "Сходимость" же во всех прочих деталях говорит только об убогости собственной мысли одного из нас - в степени, прямо пропорциональной мелкости тех деталей, в которых мы всё ещё совпадаем. Этим я, конечно, не уравниваю себя с Гради Бучем, а просто хочу сказать, что никто для приобретателя собственного знания не может быть больше, чем "ещё один источник информации". Если я хочу уметь пользоваться излагаемой Бучем теорией, то и её понимание и собственное мнение о ней мне всё равно придётся сделать самому. Его авторитет здесь не поможет.

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

Начать нам, конечно же, придётся с философии. Заявляя "объектно-ориентированное программирование" невозможно определить его не определяя предмета этого программирования. А вот тут глубокая естественность слова "объект" окажет нам медвежью услугу - понятие "объект" не имеет в ООП канонического определения!

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

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

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

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

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

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

Пишушие программисты привыкли, что "состояние" - это данные, "поведение" - код, "статический тип" - класс. Они знают, что всё это оформляется исходным текстом, предлагаемым компилятору. И компилятор генерирует команды процессора "в нужных местах", так, что "класса" в том виде, в каком его пишет программист, в откомпилированной программе - нет. Для них, естественно что объект занимает какое-то пространство в памяти, что его можно создать или уничтожить. Для них термин "статический" - относится к области видимости и "самым статическим" является "файл". Это - профессиональные стереотипы программиста, настолько, что бывает трудно допустить - реализации того же самого могут быть другими. Я и привёл пример того - не только "экземпляр статического типа" занимает память и обладает поведением, но и сам "статический тип" тоже может быть реализован не только "в понятии компилятора". А в COM - он должен быть реализован "не в понятии компилятора". Мы некогда рассматривали проблему порождения объекта сервера в коде клиента и пришли к выводу, что сделать это "общепринятым" путём не можем - на клиентской стороне компилятор не знает об объекте сервера столько информации, сколько было бы достаточно для порождения экземпляра.

Ныне рассматриваемая проблема в своей причине та же самая - у нас нет способа сообщить компилятору, который строит клиента, информацию о статическом типе объекта, существующего внутри сервера. Но она и отличается от той проблемы - экземпляр-то статического типа (вот опять, если употребить в данном контексте слова "класс" и "объект" - что они будут означать?) может естественно быть представлен его vtbl. Т.е. на стороне сервера это - "экземпляр одного из классов программы", а на стороне клиента это - vtbl, соответствующая "абстрактному классу", например. И - всё работает. А вот сам "статический тип" - он чем представляется? Для программиста в пределах одного модуля это - абстрактная категория, он не видит кода, который генерирует компилятор (если тот генерирует вообще), и не задумывается об этом: поскольку тип - статический, он, в полном соответствии с нашим определением, начинает существовать ранее, чем программа запускается на исполнение. Я упоминал, что даже в программах на C++ статический тип не есть чисто абстрактная категория - компилятор в некоторых случаях генерирует двоичные конструкции, соответствующие именно типу, а не его экземплярам. Но эти команды собираются в конструкции "пролога/эпилога", которые выполняются прозрачно до исполнения "написанной руками" части программы или - после неё. В COM получается - область видимости статического типа совпадает с областью видимости CLSID, т.е. выходит за границы модуля, бесконечна и во времени и в пространстве. Поэтому и "статический тип" должен представляться структурами времени исполнения, а не времени компиляции. Но вот вопрос - какие это должны быть структуры? Как должна выглядеть реализация этого требования ООП? Внутренний двоичный примитив "объекта", адресуемого клиентом в COM мы изучили. Это - vtbl, экспонирующая IUnknown. Именно она - "объект" в "сочленении" клиента и сервера. Но она же - "объект", а не "тип"? И других структур - нет. Что делать? Выход в том, что программный примитив нужно использовать несколько "не по назначению". А именно - нужно сделать "псевдообъект" на базе этого примитива и считать его "объектом типа". Тут возникает "проблема IUnknown" - реализация IUnknown должна быть одна в составе объекта. В прошлой статье говорилось, что это противоречие - кажущееся. Что наличие специальной процедуры порождения экземпляров статического типа полностью соответствует канонам ООП и наличие "двух IUnknown" не противоречит предыдущим 29-ти статьям. Ведь IUnknown - только программный примитив для реализации "дистанционного управления" временем существования и приведения типа. Но вот интересный вопрос - временем существования чего и приведения типа к чему? Ранее-то мы полагали, что "объекта" и "ссылки на интерфейс, реализуемый объектом". Сейчас, понимая, что "статический тип" не обязан занимать непрерывную область памяти и вполне может быть реализован как совокупность несвязных в ней фрагментов, надо уточнить - IUnknown нужно понимать в смысле ООП. Для каждой сущности - "свой" IUnknown. А "статический тип" и "экземпляр статического типа" - разные сущности, относимые, тем не менее, к одному и тому же понятию. Именно поэтому мы можем обойтись одним CLSID - контекст, когда нужно пользоваться "статическим типом" отличается от контекста использования "экземпляра", и не требует никакого раздельного именования.

Ряд читателей обратили моё внимание, что это я так коряво пытаюсь объяснить концепцию "фабрики класса". Фабрикой класса в ООП (подробно об этом - следующая статья) называется механизм, производящий экземпляры статического типа. Давайте и об этом скажем. "Объект статического типа" и "фабрика класса" соотносятся так - если "фабрика класса" у данного статического типа имеется (а её может и не быть), то она реализуется в "объекте типа". Но "объект типа" может быть и не реализовывать фабрику класса, а может - реализовывать и не только фабрику класса, но и что-то ещё. Можно сказать и иначе: фабрика класса - концепция, идея, алгоритм. А "объект типа" - реализация.

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

Рассмотренное - один из очень сложных вопросов COM. ООП - теория, а COM - инженерная реализация этой теории. И нужно уметь (что и определяет квалификацию программиста) работать на разных уровнях абстракции, рассматривая целостное явление с разных сторон и понимая, что каждый взгляд описывает только часть целого. Я повторюсь - к COM нельзя подходить с мерками только языка программирования: у вас либо клиент, либо сервер - уже есть просто "нечто", обладающее и состоянием и поведением, состоящее из кода и данных, занимающее память... но - не описываемое на том языке на котором вы работаете. Объект сервера, который вы адресуете из клиента, в контексте клиента описывается "классом". Но каким? Тем ли, который использован в контексте сервера для реализации этого объекта? Значит, представления об одном и том же "объекте" (в отличие от любого объектно-ориентированного языка) на стороне клиента и сервера - разные? И как в таком случае надлежит понимать "объект"?

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

В следующей же статье - о фабрике класса в COM.

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

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

форум

технология COM

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


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