"Сущность технологии СОМ. Библиотека программиста" - читать интересную книгу автора (Бокс Дональд)Отделение интерфейса от реализацииКонцепция инкапсуляции основана на разделении того, как объект выглядит (его интерфейса), и того, как он в действительности работает (его реализации). Проблема в C++ в том, что этот принцип неприменим на двоичном уровне, так как класс C++ одновременно является и интерфейсом, и реализацией. Этот недостаток может быть преодолен, если смоделировать две новые абстракции, являющиеся классами C++, но различающиеся по своей сущности. Если определить один класс C++ как интерфейс для типа данных, а второй – как саму реализацию типа данных, то конструктор объектов теоретически может модифицировать некоторые детали класса реализации, в то время как класс интерфейса останется неизменным. Все, что нужно, – это выдержать соотношение интерфейса с его реализацией так, чтобы не показывать клиенту никаких деталей реализации. Класс интерфейса должен содержать только такое описание основных типов данных, какое должен, по мнению разработчика, представлять себе клиент. Поскольку интерфейс не должен сообщать ни о каких деталях реализации, класс интерфейса C++ не может содержать никаких элементов данных, которые могут быть использованы в реализации объекта. Вместо этого класс интерфейса должен содержать только описания методов для каждой открытой операции объекта. Класс реализации C++ будет содержать фактические элементы данных, необходимые для обеспечения функционирования объекта. Одним из простейших подходов является использование класса-дескриптора (handle-class) в качестве интерфейса. Класс-дескриптор мог бы просто содержать непрозрачный (opaque) указатель, чей тип никогда не может быть полностью определен клиентом. Следующее определение класса демонстрирует эту технику: // FastStringItf.h class declspec(dllexport) FastStringItf { class FastString; // introduce name of impl. class // вводится имя класса реализации FastString *mpThis; // opaque pointer (size remains constant) // непрозрачный указатель (размер остается постоянным) public: FastStringItf(const char *psz); ~FastStringItf(void); int Length(void) const; // returns # of characters // возвращает число символов int Find(const char *psz) const; // returns offset // возвращает смещение }; Заметим, что двоичное представление этого класса интерфейса не меняется с добавлением или удалением элементов данных из класса реализации // faststringitf.срр // (part of DLL, not client) // (часть DLL, а не клиента) #include «faststring.h» #include «faststringitf.h» FastStringItf::FastStringItf(const char *psz) : mpThis(new FastString(psz)) { assert(mpThis != 0); } FastStringItf::~FastStringItf(vo1d) { delete mpThis; } int FastStringItf::Length(void) const { return mpThis-gt;Length(); } int FastStringItf::Find(const char *psz) const { return mpThis-gt;Find(psz); } Эти передающие методы должны быть транслированы как часть DLL Рисунок 1.4 показывает, как использовать классы-дескрипторы для отделения интерфейса от реализации на этапе выполнения. Заметим, что косвенный подход, введенный классом интерфейса, устанавливает двоичную защитную стену (firewall – брандмауэр) между клиентом и реализацией объекта. Эта двоичная стена очень точно описывает, как клиент может сообщаться с реализацией. Все связи клиент-объект осуществляются через класс интерфейса, который содержит очень простой двоичный протокол для входа в область реализации объекта. Этот протокол не содержит никаких деталей класса реализации в C++. Хотя методика использования классов-дескрипторов имеет свои преимущества и безусловно приближает нас к возможности безопасного извлечения классов из DLL, она также имеет свои недостатки. Отметим, что класс интерфейса вынужден явно передавать каждый вызов метода классу реализации. Для простого класса вроде |
||||
|