Сущность технологии СОМ. Библиотека программиста - Страница 12
return pfs2->FindN(«Bob», 10);
else {
// object doesn't derive from IFastString2
// объект не порожден от IFastString2
error(«Cannot find 10th occurrence of Bob»);
return -1;
}
Если объект порожден от расширенного интерфейса, то оператор dynamic_cast возвращает указатель на вариант объекта, совместимый с IFastString2, и клиент может вызвать расширенный метод объекта. Если же объект не порожден от расширенного интерфейса, то оператор dynamic_cast возвратит пустой (null) указатель. В этом случае клиент может или выбрать другой способ реализации, зарегистрировав сообщение об ошибке, или молча продолжить без расширенной операции. Эта способность назначенного клиентом постепенного сокращения возможностей очень важна при создании гибких динамических систем, которые могут обеспечить со временем расширенные функциональные возможности.
Иногда требуется раскрыть еще один аспект функциональности объекта, тогда разворачивается еще более интересный сценарий. Обсудим, что следует предпринять, чтобы добавить постоянства, или персистентности (persistence), классу реализации IFastString. Хотя, вероятно, можно добавить методы Load и Save к расширенной версии IFastString, другие типы объектов, не совместимые с IFastString, могут тоже быть постоянными. Простое создание нового интерфейса, который расширяет IFastString:
class IPersistentObject : public IFastString
{
public: virtual bool Load(const char *pszFileName) = 0;
virtual bool Save(const char *pszFileName) = 0;
};
требует, чтобы все постоянные объекты поддерживали также операции Length и Find. Для некоторого, весьма малого подмножества объектов это могло бы иметь смысл. Однако для того, чтобы сделать интерфейс IPersistentObject возможно более общим, он должен быть своим собственным интерфейсом, а не порождаться от IFastString:
class IPersistentObject
{
public: virtual void Delete(void) = 0;
virtual bool Load(const char *pszFileName) = 0;
virtual bool Save(const char *pszFileName) = 0;
};
Это не мешает реализации FastString стать постоянной; это просто означает, что постоянная версия FastString должна поддерживать оба интерфейса: и IFastString, и IPersistentObject:
class FastString : public IFastString, public IPersistentObject
{
int m_cch;
// count of characters
// счетчик символов
char *m_psz;
public: FastString(const char *psz);
~FastString(void);
// Common methods
// Общие методы
void Delete(void);
// deletes this instance
// уничтожает этот экземпляр
// IFastString methods
// методы IFastString
int Length(void) const;
// returns # of characters
// возвращает число символов
int Find(const char *psz) const;
// returns offset
// возвращает смещение
// IPersistentObject methods
// методы IPersistentObject
bool Load(const char *pszFileName);
bool Save(const char *pszFileName);
};
Чтобы записать FastString на диск, пользователю достаточно с помощью RTTI связать указатель с интерфейсом IPerststentObject, который выставляется объектом:
bool SaveString(IFastString *pfs, const char *pszFN)
{
bool bResult = false;
IPersistentObject *ppo = dynamic_cast
if (ppo) bResult = ppo->Save(pszFN);
return bResult;
}
Эта методика работает, поскольку транслятор имеет достаточно информации о представлении и иерархии типов класса реализации, чтобы динамически проверить объект для выяснения того, действительно ли он порожден от IPersistentObject. Но здесь есть одна проблема.
RTTI – особенность, сильно зависящая от транслятора. В свою очередь, DWP передает синтаксис и семантику RTTI, но каждая реализация RTTI разработчиком транслятора уникальна и запатентована. Это обстоятельство серьезно подрывает независимость от транслятора, которая была достигнута путем использования абстрактных базовых классов как интерфейсов. Это является неприемлемым для архитектуры компонентов, не зависимой от разработчиков. Удачным решением было бы упорядочение семантики dynamic_cast без использования свойств языка, зависящих от транслятора. Явное выставление хорошо известного метода из каждого интерфейса, представляющего семантический эквивалент dynamic_cast, позволяет достичь желаемого эффекта, не требуя, чтобы все объекты использовали тот же самый транслятор C++:
class IPersistentObject
{
public: virtual void *Dynamic_Cast(const char *pszType) = 0;
virtual void Delete(void) = 0;
virtual bool Load(const char *pszFileName) = 0;
virtual bool Save(const char *pszFileName) = 0;
};
class IFastString
{
public: virtual void *Dynamic_Cast(const char *pszType) = 0;
virtual void Delete(void) = 0;
virtual int Length(void) = 0;
virtual int Find(const char *psz) = 0;
};
Так как всем интерфейсам необходимо выставить этот метод вдобавок к уже имеющемуся методу Delete, имеет большой смысл включить общее подмножество методов в базовый интерфейс, из которого могли бы порождаться все последующие интерфейсы:
class IExtensibleObject { public: virtual void *Dynamic_Cast(const char* pszType) = 0; virtual void Delete(void) = 0; }; class IPersistentObject : public IExtensibleObject { public: virtual bool Load(const char *pszFileName) = 0; virtual bool Save(const char *pszFileName) = 0; }; class IFastString : public IExtensibleObject { public: virtual int Length(void) = 0; virtual int Find(const char *psz) = 0; };
Имея такую иерархию типов, пользователь может динамически запросить объект о данном интерфейсе с помощью следующей не зависящей от транслятора конструкции:
bool SaveString(IFastString *pfs, const char *pszFN) { boot bResult = false; IPersistentObject *ppo = (IPersistentObject) pfs->Dynamic_Cast(«IPers1stentObject»); if (ppo) bResult = ppo->Save(pszFN); return bResult; }
В только что приведенном примере клиентского использования присутствуют требуемая семантика и механизм для определения типа, но каждый класс реализации должен выполнять это функциональное назначение самолично:
class FastString : public IFastString, public IPersistentObject
{
int m_cсh;
// count of characters
// счетчик символов
char *m_psz;
public:
FastString(const char *psz);
~FastString(void);
// IExtensibleObject methods
// методы IExtensibleObject
void *Dynamic_Cast(const char *pszType);
void Delete(void);
// deletes this instance
// удаляет этот экземпляр
// IFastString methods
// методы IFastString
int Length(void) const;
// returns # of characters
// возвращает число символов
int Find(const char *psz) const;
// returns offset
// возвращает смещение
// IPersistentObject methods
// методы IPersistentObject
bool Load(const char *pszFileName);
bool Save(const char *pszFileName);
};
Реализации Dynamic_Cast необходимо имитировать действия RTTI путем управления иерархией типов объекта. Рисунок 1.8 иллюстрирует иерархию типов для только что показанного класса FastString. Поскольку класс реализации порождается из каждого интерфейса, который он выставляет, реализация Dynamic_Cast в FastString может просто использовать явные статические приведения типа (explicit static casts), чтобы ограничить область действия указателя this, основанного на подтипе, который запрашивается клиентом: