Анимация
JavaScript
|
Главная Библионтека void DoSomething(); void DoSomethingElse(); class PFoo { private: Foo* foo; public: PFoo() : foo(new Foo) {} PFoo(const PFoo& pf) : foo(new Foo(*(pf.foo))) {} ~PFoo() { delete Foo; } PFoo& operator=(const PFoo& pf) if (this != &pf) { delete foo; foo = new Foo(*(pf.foo)); return *this; void DoSomething() { foo->DoSomething(); } void DoSomethingElse() { foo->DoSomethingElse(); } Произошло следующее: мы воспользовались удобными средствами копирования/вставки текста вашей среды программирования и продублировали в указателе интерфейс указываемого объекта. Чтобы не лениться и не взваливать всю тяжелую работу по делегированию на оператор ->, мы решительно реализовали все функции класса так, что каждая из них перенаправляет вызов функции-прототипу указываемого объекта. Указатели, воспроизводящие интерфейс указываемого объекта, называются интерфейсными указателями (interface pointers). Маскировка указываемого объекта Поначалу кажется, что реально мы ничего не добились. Чтобы подставляемые функции работали, интерфейс класса Foo все равно должен находиться в файле .h перед объявлением класса PFoo. Тем не менее, смирившись с небольшими дополнительными вычислениями для наших указателей, мы получаем быстрый и ощутимый результат. class Fool; Все, что клиент видит и знает о Foo class PFool { private: Fool* foo; public: PFool(); PFoo1(const PFoo1& pf); ~PFoo(); PFoo1& operator=(const PFoo1& pf); void DoSomething(); void DoSomethingElse(); class Foo1 { friend class PFoo1; protected: Foo1(); public: void DoSomething(); void DoSomethingElse(); PFoo1::PFoo1() : foo(new Foo1) PFoo1::PFoo(const PFoo1& pf) : foo(new Foo1(*(pf.foo))) PFoo1::~PFoo() delete foo; PFoo1& PFoo1::operator=(const PFoo1& pf) if (this != &pf) { delete foo; foo = new Foo1(*(pf.foo)); return *this; void PFoo1::DoSomething() foo->DoSomething(); void PFoo1::DoSomethingElse() foo->DoSomethingElse(); Foo1::Foo1() void Foo1::DoSomething() cout << "Foo::DoSomething()" << endl; void Foo1::DoSomethi ngElse() cout << "Foo::DoSomethingElse()" << endl; Видите, что здесь происходит? Для клиента класс Foo перестает существовать. Для всех практических целей указатель стал самим объектом. С таким же успехом мы могли все переименовать, убрать Р перед указателем и заменить имя Foo чем-нибудь более закрытым и загадочным. Единственное, что говорит о существовании второго класса, - предварительное объявление class Foo;. Цена всего происходящего - вызов не подставляемых (noninline) функций в каждой функции класса указателя. Для некоторых немногочисленных приложений и классов даже эта малая цена может стать неприемлемой. В таких случаях существуют две альтернативы для повышения скорости: использование умных указателей на базе оператора -> и использование интерфейсных указателей с занесением объявления класса указываемого объекта в файл .h и отказом от всех преимуществ инкапсуляции. Как вы убедитесь в оставшейся части этой главы, второй вариант все же имеет некоторые достоинства. Изменение интерфейса Одно из преимуществ интерфейсных указателей - в том, что они фактически позволяют изменить интерфейс указываемого объекта без внесения изменений в его класс. Интерфейс, представленный интерфейсным указателем, находится полностью в вашем распоряжении; вы можете исключить из него некоторые функции указываемого объекта, изменить сигнатуры, добавить ваши собственные дополнительные функции и просто хорошо провести время, заново изобретая указываемый объект. Грани Многие библиотеки классов (особенно связанные с графическим интерфейсом пользователя) содержат большие классы из десятков, а то и сотен функций. Визуальная часть экрана в них называется по-разному - видом, окном или панелью. Ниже показан типичный набросок класса, который представляет это отображаемое нечто, как бы оно ни называлось. class View { На практике будет производным от другого класса protected: Часть, предназначенная только для производных классов вида public: Функции конструирования и инициализации Функции уничтожения и деактивизации Общие функции объекта Обработка событий Функции отображения Геометрия («где сработала мышь?») Управление иерархией видов Каждый подраздел может насчитывать до нескольких десятков функций. Разбираться в этих функциях - все равно что блуждать в зеркальном лабиринте; куда бы вы ни повернулись, виднеются бесчисленные отражения одного и того же класса. Конечно, такой класс можно было бы организовать и более разумно - например, выделить каждое семейство интерфейсов в собственный базовый класс и затем объединить эти классы с помощью множественного наследования. Или построить комплекс из объектов, совместная работа которых основана на взаимном делегировании. Все эти варианты обладают своими недостатками. • Пользователи должны помнить, как объединяются все фрагменты каждого составного класса. Вы уверены, что действительно хотите этого? • Когда фрагменты объединяются в общий класс, от которого затем создаются производные классы, проблема возникает заново на другом уровне иерархии классов. • Нелегко заставить один базовый класс правильно работать с другим, когда эти два класса не имеют ничего общего до их объединения в контексте некоторого составного класса. • Проектирование больших комплексов взаимодействующих объектов - занятие не для слабонервных. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 [ 27 ] 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |