|
Программирование >> Оптимизация возвращаемого значения
Если понятие о реализации абстрактной функции вас удивляет, вы просто недостаточно разбираетесь в данном вопросе. Объявление функции абстрактной не означает, что она не имеет реализации, это означает, что: □ текущий класс является абстрактным □ и любой наследующий от него реальный класс должен объявлять эту функцию как обычную виртуальную функцию (то есть без =0). Хотя большинство абстрактных функций никогда не реализуются, абстрактные деструкторы представляют собой особый случай. Они должны быть реализованы, поскольку всегда вызываются при вызове деструктора производного класса. Кроме того, они часто выполняют полезные действия, такие как высвобождение ресурсов (см. правило 9) или запись сообщений в лог-файл. Хотя реализация абстрактных функций в общем случае встречается довольно редко, для абстрактных деструкторов это не только обычно, но и обязательно. Возможно, вы заметили, что рассказ о присваивании при помощи указателей базового класса основан на предположении, что реальные базовые классы, такие как Animal, содержат элементы данных. Если элементов данных нет, то проблемы не существует, и будет безопаснее сделать реальный класс наследником другого реального класса без данных. Существуют два варианта дальнейшего существования класса без данных, который был бы реальным базовым классом: может ли он в будущем содержать элементы данных или нет. Если да, то можно просто отложить проблему до тех пор, пока в класс не будут добавлены элементы данных, в этом случае вы получаете сиюминутную выгоду за счет возможных неприятностей в будущем (см. также правило 32). С другой стороны, если базовый класс точно не будет никогда иметь элементов данных, то похоже, что он скорее должен быть абстрактным классом. Что толку от реального базового класса без данных? Замена такого реального базового класса, как Animal, на абстрактный базовый класс типа Abstract Animal не просто облегчает понимание поведения operator=. Это также уменьшает вероятность того, что вы попытаетесь обращаться с массивами полиморфно; неприятные последствия такого подхода были рассмотрены в правиле 3. Но более важный выигрыш от использования этого метода обнаруживается на уровне разработки, так как замена реальных базовых классов абстрактными заставляет явно выделять полезные абстракции. То есть это заставляет вас создавать новые абстрактные классы на основе полезных понятий, даже если вы не осознаете их существование. Если имеются два реальных класса С1 и С2, и вы ( л ) хотите, чтобы класс С2 открыто наследовал от класса С1, вам нужно преобразовать иерархию, состоящую из двух классов, в иерархию с тремя классами, создав новый абстрактный класс А и сделав оба класса С1 сГ) Гс2 -2 6Г0 открытыми наследниками (см. рис. 6.3). Данное изменение заставляет вас определить абрис. 6.3 страктный базовый класс А, и это главное. Очевидно, что классы С1 и С2 имеют что-то общее, поэтому они и связаны открытым наследованием. Чтобы выполнить такое преобразование, вы должны определить, в чем эта общность заключается. Кроме того, вы должны формализовать это что-то в виде класса C+-I-. В результате что-то становится не просто чем-то неопреде-деленным, а пол5ает статус формальной абстракции, имеющей определенные функции-члены и определенную семантику. Но каждый класс представляет некоторый тип абстракции, поэтому не должны ли мы создавать по два класса для каждого понятия в иерархии, один абстрактный (воплощающий абстрактную часть абстракции) а второй - реальный (воплощающий часть абстракции, связанную с созданием объектов)? Нет, не должны. Если сделать это, полученная иерархия будет содержать слишком много классов. Такую иерархию сложно понимать, поддерживать и компилировать, что противоречит целям объектно-ориентированного программирования. Цель его состоит в том, чтобы определить полезные абстракции и вводить их -и только их - в абстрактные классы. Но как выделить полезные абстракции? Кто может знать, какие абстракции окажутся полезными в будущем? Кто может предсказать, кто будет наследовать и от чего? Я не знаю, как предсказать будущее использование иерархии наследования, однако уверен: необходимость абстракции в одной ситуации может быть случайной, но если абстракция требуется в нескольких случаях, это обычно является значимым. Таким образом, полезные абстракции - те абстракции, которые полезные в различных ситуациях. Они соответствуют классам, которые полезны как сами по себе (то есть нужны объекты этого типа), так и для того, чтобы создавать от них производные классы. Это в точности соответствует выгоде от преобразования реального базового класса в абстрактный: такое преобразование заставляет вводить новый абстрактный класс, только если существующий реальный класс будет применяться в качестве базового, то есть когда класс будет (повторно) использоваться в новой ситуации. Как было показано, такие абстракции полезны. Когда нужно просто ввести новое понятие, мы не можем оправдать создание одновременно и абстрактного класса (для самого понятия) и реального класса (для объектов, соответствующих этому понятию), но когда оно потребуется во второй раз, оправдание налицо. Описанное преобразование просто автоматизирует этот процесс и заставляет разработчиков и программистов явно представлять полезные абстракции, даже если они еще не точно представляют, какие понятия пригодятся в будущем. Также оказывается, что при этом гораздо проще обеспечить разумное поведение операторов присваивания. Проанализируем короткий пример. Допустим, вы разрабатываете приложение, которое занимается переносом данных между компьютерами в локальной сети, разбивая их на пакеты и передавая в соответствии с некоторым протоколом. (Здесь будет рассмотрен только класс или классы для представления пакетов.) Предположим далее, что имеется только один тип протокола передачи и только один тип пакетов. Возможно, вы слышали о существовании других протоколов и типов пакетов, но никогда их не поддерживали и не собираетесь делать этого в будущем. Должны ли вы создавать абстрактные классы пакетов (для представления понятия пакета) наряду с реальными классами для пакетов, которые точно будете использовать? Если вы сделаете это, то сможете затем добавлять новые типы пакетов, не изменяя соответствующий базовый класс. Это избавит вас от необходимости после добавления нового типа пакетов перекомпилировать все использующие пакеты приложения. Но такой подход требует создания двух классов, а сейчас вам нужен только один (для используемого типа пакетов). Стоит ли сейчас усложнять схему с тем, чтобы разрешить будущее развитие, которое, возможно, и не потребуется? В данном случае не существует однозначно правильного выбора, но практика показывает, что не получается создавать хорошие классы для понятий, которые мы недостаточно понимаем. Если вы создадите абстрактный класс для пакетов, насколько вероятно, что вы сделаете его таким, как нужно, в особенности, если ваш опыт ограничивается только одним типом пакетов? Помните, что вы получите выигрыш от использования абстрактного класса для пакетов, только если разработаете его так, чтобы будущие классы могли наследовать от него без изменения его самого. (Если потребуется изменение абстрактного класса, то вам придется перекомпилировать весь код, использующий пакеты, и вы ничего не выиграете.) Маловероятно, что вам удастся разработать удовлетворительный абстрактный класс пакетов, если вы не слишком хорошо разбираетесь в различных типах пакетов и ситуациях, когда они используются. В этом случае, учитывая ваш ограниченный опыт, я бы посоветовал вам не определять абстрактный класс для пакетов, добавив его позже, если вам потребуется выполнить наследование от реального класса пакета. Описанное преобразование является одним, но не единственным способом определить необходимость абстрактных классов. Существует много других методик выявить приемлемые кандидатуры для абстрактных классов (вы найдете их в книгах по объектно-ориентированному анализу). Абстрактные классы можно вводить не только для того, чтобы сделать реальный класс наследником другого реального класса. Но желание связать два реальных класса при помощи открытого наследования обычно указывает на необходимость создания нового абстрактного класса. Как это часто бывает, реальность в этом случае грубо вторгается в мирное теоретическое размышление. Библиотеки классов С++ от независимых производителей плодятся очень быстро, и что, если вы захотите создать реальный класс, наследующий от реального класса в библиотеке, к которой у вас есть доступ только на чтение? Вы не можете изменить библиотеку, добавив в нее новый абстрактный класс, поэтому ваш выбор ограничен и непривлекателен: □ сделать реальный класс производным от существующего реального класса и примириться с проблемами, присущими присваиванию, которые обсуждались
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |