Шаг 9 - Множественные интерфейсные smart-указатели

Вполне возможно, что Вы работаете с действительно крупным проектом, иерархия классов развилась до огромных размеров, а каждый класс (особенно внизу иерархии) обладает десятками или сотнями открытых функций. Конечно, неплохо задаться вопросом "Насколько разумно это?". Еще лучше, если этот вопрос задать до начала написания кода. Тогда можно вовремя почитать Гради Буча, установить на компьютер "Rational Rose" или сходную CASE-систему, и более чем тщательно спроектировать иерархию классов. К сожалению, вопросы объектного анализа и проектирования выходят далеко за рамки данного Шага и моих способностей. Но на всякий случай сообщу, что Microsoft серьезнейшим образом почистила библиотеку своих классов при выпуске версии для карманных компьютеров, и только в результате такой меры ЭТО стало вообще работать в каких-то разумных пределах и объемах; ошибки этапа моделирования вообще обходятся очень дорого впоследствии, особенно если система развивается.

Тем не менее, функций в классах остается достаточно много. Очевидно, что они группируются по своему назначению. Практически всегда есть группы, отвечающие за:

  1. конструирование и инициализацию;
  2. уничтожение и деактивацию;
  3. сохранение и загрузку;
  4. отображение;
  5. обработку сообщений (событий).

Тут можно провести такую аналогию: если каждую функцию представить в виде одного провода, то их можно объединить в стандартный разъем: LPT, RS-232 или иной, и этот разъем будет обладать новыми, высшими свойствами, какими по отдельности провода не обладают; объединяя функции в цельные функциональные наборы мы так же получаем нечто новое. Присвоим этим наборам название, потом займемся реализацией. Термин возьмем у Microsoft. Необычно, нетривиально, метко, а главное, свежо: интерфейс. Элджер дает термин 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. Если Ваш проект действительно серьезный, то без грамотной модели он не существует. Этель Лилиан Войнич никогда бы не дописала свой роман "Овод", если бы с самого начала не задалась моделью: класс <солдат> обязательно должен иметь функцию <застрелить> экземпляр класса <мятежник>, а класс <офицер> во-первых, наследует от класса <солдат>, во-вторых, может отсортировать набор <солдат> в порядке возрастания - построить их на расстрел, в третьих - может и сам пристрелить врага при необходимости. Сильно подозреваю, что в этой модели функция <застрелить> является для <мятежника> дружественной. Выбирайте друзей правильно!


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Albert Makhmutov - 04.03.2001