нетривиально, метко, а главное, свежо: интерфейс. Элджер дает термин facet (грань), и suite (комплект, а не костюм). Где-то я еще видел термин sub-pointer, но этот термин применим только для одной реализации, но не отражает общей концепции. По счастью, именно об этой реализации мы и собираемся поговорить.
Итак, как же объединить функции-члены в наборы, опираясь на средства языка? Да просто: определить их в абстрактных базовых классах, а потом объединять их при помощи множественного наследования. Это вполне неплохая идея. Именно так создаются объекты на основе ATL: дается набор стандартных шаблонов для стандартных интерфейсов, потом объединяется при помощи множественного наследования. Указатели на интерфейсы Вы можете легко получать при помощи dynamic_cast‹T›, только на всякий случай обрабатывайте исключение (обратного преобразования так легко не сделать, к сожалению; вообще это проблема - преобразование базового класса в производный в случае множественного наследования; я собираюсь поговорить об этом позже, а в этом Шаге заклинаю Вас не использовать явного преобразования указателей, только dynamic_cast‹T› с перехватом исключения и проверкой на NULL). Но есть и недостатки, причем кроме чисто технических, на мой взгляд, есть еще один серьезный этап проектирования: множественное наследование в объектном анализе, применяется для реализации какого-то одного, редкого аспекта поведения, присущего разным несвязанным классам - 'повадки' класса. Наследование от класса, склеенного из нескольких других, выведет проблему на новый уровень иерархии. Вывод - такая техника оправдана в листьях дерева классов, в самом низу иерархии.
Вариант номер два: использование нескольких умных указателей (далее я буду также называть smart-указателями или smart-pointer), каждый из которых предоставляет доступ к ограниченному, но функционально полному набору функций-членов (это называется полный минимальный интерфейс, то есть предоставляющий все необходимое, и ничего свыше совершенно необходимого). Вы уже знаете о них достаточно, и приводить подробный код будет теперь несколько несерьезно, разве что самые общие черты:
class Cоbject {
// функции набора 1
// функции набора 2
};
class Cfuncset1 {
private:
Cobject* obj;
public:
// функции набора 1
};
class Cfuncset2 {
private:
Cobject* obj;
public:
// функции набора 2
};
Обращаю внимание - использовать нужно именно простые умные указатели, или интерфейсные указатели из Шага 7, но не ведущие. Проблемы этого варианта очевидны, хотя бы чисто технические: преобразование интерфейсов и создание-удаление реального указываемого объекта. Smart-указатели тоже можно наследовать от абстрактных базовых классов, тогда Вы получите определенную жесткость своей конструкции. Сам указываемый объект может быть каким угодно. Склеили ли вы его через множественным наследование, или как единое целое он вообще не существует, а реализован как несколько жестко связанных объектов (бывает и такое) - клиентам это без разницы, если они все равно имеют к нему доступ через стандартные интерфейсы.
Подробности разберем в следующих Шагах, а сейчас я призываю Вас вспомнить, как в COM/DCOM сделано сохранение объектов: Вы имеете IStorage - умный указатель на объект 'файл-хранилище', IPersist - умный указатель на объект, подлежащий записи, затем натурально сцепляете их, передавая указатель на IStorage в IPersist, и теперь Ваш объект просто выливается в свое хранилище, как молоко в глиняный кувшин. Объект может иметь любое количество иных интерфейсов, но наличие стандартного IPersist позволяет легко и красиво выполнить стандартную операцию.
Последнее: читайте Гради Буча! Учтите, что с первого раза он никогда (вообще!) не доходит. Только на второй или третий, не меньше, и то, если будете перемежать его с UML. Если Ваш проект действительно серьезный, то без грамотной модели он не существует. Этель Лилиан Войнич никогда бы не дописала свой роман 'Овод', если бы с самого начала не задалась моделью: класс ‹солдат› обязательно должен иметь функцию ‹застрелить› экземпляр класса ‹мятежник›, а класс ‹офицер› во- первых, наследует от класса ‹солдат›, во-вторых, может отсортировать набор ‹солдат› в порядке возрастания - построить их на расстрел, в третьих - может и сам пристрелить врага при необходимости. Сильно подозреваю, что в этой модели функция ‹застрелить› является для ‹мятежника› дружественной. Выбирайте друзей правильно!
Шаг 10 - Множественные интерфейсные указатели. Продолжение.
Humpty-Dumpty: 'With a name of Your,
You might be any shape, almost!'
Сейчас мы поговорим о реализации, но до начала позвольте мне вернуться немного назад и добавить, что есть еще один неплохой способ организации множества интерфейсов. Он выглядит слегка неуклюже, но при известной дисциплине вполне работает: это метод вложенных классов из MFC COM. В самых общих чертах - там применяется явное получение смещения родительского класса от вложенного. Желающие могут посмотреть в MSDN по ключевому слову METHOD_PROLOGUE.
В пределах данного Шага я использую термин 'Интерфейс' в смысле 'smart-указатель', а термин 'объект' в смысле 'сложный указываемый объект', несмотря на то, что меня тошнит от этих слов. Если у Вас есть более подходящие, пишите, буду счастлив. Код я снова не проверяю, здесь нет ничего такого сложного, важна лишь идея.
Итак, мы уже решили иметь к некоему сложному объекту набор стандартных интерфейсов, и реализовать их при помощи smart-указателей. Но сами по себе они не имеют никакой пользы, если мы не сможем получать интерфейсы и объект друг из друга; для этого надо заиметь специальные средства, ибо интерфейсные указатели есть самостоятельные объекты, и вообще могут изменять свой указываемый объект в течение своего существования, и даже изменять тип объекта, если угодно (этого не может даже BASIC).
Для получения интерфейса по объекту проще всего нарисовать конструктор, получающий в качестве аргумента объект:
// Это объект
CComplexObject {};
// Это интерфейс