|
Программирование >> Разработка устойчивых систем
Джерри Шварц (Jerry Schwarz), автор библиотеки потоков ввода-вывода, неоднократно говорил, что если бы ему пришлось проектировать библиотеку заново, то он бы убрал множественное наследование из ее архитектуры и заменил его различными потоковыми буферами и операторами преобразования. Класс А в этом случае является (непосредственно) базовым по отношению к В, поэтому имя В::f доминирует над A::f. Отказ от множественного наследования Принимая решение о применении множественного наследования, следует задать себе по крайней мере два вопроса. Должен ли новый тип предоставлять открытые интерфейсы обоих классов? (Подумайте, нельзя ли вложить один класс в другой, чтобы в новом классе проявлялась лишь часть его интерфейса.) Потребуется ли выполнять повышаюшее преобразование к обоим базовым классам (или к большему количеству базовых классов)? Если хотя бы на один вопрос вы ответите нет , то без множественного наследования можно - и, скорее всего, нужно - обойтись. Обращайте особое внимание на ситуации, в которых один класс должен проходить повышаюшее преобразование только как аргумент функции. В этом слзд1ае класс можно оформить как вложенный и включить в новый класс функцию автоматического преобразования типа для получения ссылки на вложенный объект. Всюду, где объект нового класса передается в аргументе функции, рассчитывающей получить вложенный объект, используется функция преобразования типа. Тем не менее, преобразование типа не годится для обычного полиморфного выбора функции; для этого необходимо наследование. Правила хорошего стиля проектирования гласят, что композиции следует отдавать предпочтение перед наследованием. Расширение интерфейса Одно из лучших применений множественного наследования связано с использованием кода, находящегося вне вашего контроля. Предположим, вам досталась библиотека с заголовочным файлом и откомпилированными функциями, но без исходных текстов. Библиотека представляет собой иерархию классов с виртуальными функциями и содержит глобальные функции, получающие указатели на базовый класс библиотеки; иначе говоря, библиотечные объекты используются полиморфно. Теперь допустим, вы строите на базе этой библиотеки свое приложение и пишете собственный код с полиморфным применением базового класса. Позднее, на стадии разработки или сопровождения проекта, вдруг выясняется, что интерфейс базового класса, предоставляемый разработчиком, вас не устраивает: какая-нибудь его невиртуальная функция должна быть виртуальной, или же в нем отсутствует виртуальная функция, необходимая для решения вашей задачи. Возможно, вам поможет множественное наследование. Рассмотрим примерный заголовочный файл библиотеки: : С09:Vendor.h Заголовок класса, предоставленный разработчиком. В вашам распоряжении имеется только он и откомпилированный файл Vendor.obj. fifndef VENDOR H fdefine VENDOR H class Vendor { public: virtual void v() const: void f() const: Может, функцию нужно сделать виртуальной... -VendorO: Деструктор не виртуален! class Vendorl : public Vendor { public: void v() const: void f() const: -VendorlO: void A(const Vendors): void BCconst Vendors): И т. д. fendif VENDOR H 111 - Конечно, библиотека в действительности содержит больше производных классов и обладает более обширным интерфейсом. Обратите внимание на функции А() и В(), которые получают ссылку на базовый класс и интерпретируют ее полиморфно. Вот как выглядит файл реализации библиотеки: : С09:Vendor.срр {0} Этот код откомпилирован и недоступен для пользователя библиотеки. findude Vendor.h finclude <iostreani> using namespace std: void Vendor::v() const { cout Vendor::v() endl: } void Vendor::f() const { cout Vendor::f() endl: } Vendor::-VendorО { cout -VendorO endl: } void Vendorl::vO const { cout Vendorl::v() endl: } void Vendorl::f() const { cout Vendorl::f() endl: } Vendorl: :~Vendorl() { cout -VendorlO endl: } void ACconst Vendors V) { ... V.vO: V.fO: .. void B(const Vendors V) { ... V.vO: V.fO: .. } /:- Но в вашем проекте исходные тексты недоступны. Вместо этого вы получаете откомпилированный файл Vendor.obj или Vendor.lib (или с другим расширением, принятым в вашей системе). При использовании библиотеки возникают проблемы. Во-первых, деструктор базового класса не является виртуальным. Во-вторых, функция f() тоже не является виртуальной; видимо, разработчик библиотеки решил, что это не обязательно. В-третьих, в интерфейсе базового класса отсутствует функция, абсолютно необходимая для решения вашей задачи. Также предположим, что вы уже написали довольно-таки объемистый код с использованием сушествуюшего интерфейса (не говоря уже о неподконтрольных вам функциях А() и В()), и изменять его не хочется. Чтобы выйти из положения, создайте собственный интерфейс класса и новый набор производных классов от вашего интерфейса и сушествующих классов: : C09:Paste.cpp {L} Vendor Решение проблемы с использованием множественного наследования finclude <1ostreani> linclude Vendor.h using namespace std: class MyBase { Исправление интерфейса Vendor public: virtual void vO const = 0: virtual void fO const = 0: Новая интерфейсная функция: virtual void g() const = 0: virtual -MyBaseO { cout ~MyBase()\n : } class Pastel : public MyBase. public Vendorl { public: void vO const { cout Pastel::v() endl: Vendorl::v(): void fO const { cout Pastel::f() endl: Vendorl::f(): void g() const { cout Pastel::g() endl: -PastelО { cout -PastelO endl: } int mainO { Pastels pip = *new Pastel: MyBaseS mp = pip: Повышающее преобразование cout calling f()\n : mp.fO: Правильное поведение cout calling g()\n : mp.gO: Новое поведение cout calling A(plp)\n : Мы встречали такое в коммерческих библиотеках С++, по крайней мере, в ранних.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |