При наследовании абстрактность сохраняется: если класс-наследник не реализует унаследованную чистую виртуальную функцию, то он тоже является абстрактным. В С++ абстрактный класс выражает понятие интерфейса. Наследование от абстрактного класса — это наследование интерфейса. По этой причине абстрактные классы часто являются вершиной иерархии классов, предоставляя общий базовый интерфейс для всей иерархии. Для музыкальных инструментов базовый класс Instrument, очевидно, должен быть абстрактным классом, в котором определены две чистые виртуальные функции:
class Instrument { public:
virtual void play(note) const = 0: // играть ноту
virtual string name() const = 0: // выдать название
}:
Рассмотрим простой пример наследования от абстрактных классов (листинг 9.6), и заодно продемонстрируем изменение возвращаемого значения виртуальной функции.
Листинг 9.6. Наследование от абстрактного класса
class Food // абстрактный класс "Пища животного"
{ public:
virtual string typeFoodO const = 0: // тип пищи
};
Герб Саттер в [21] и Скотт Мейерс в [23] называют такие классы классами-протоколами.
class Animal { public:
virtual string kind() const = 0;
virtual Food& eats() = 0;
// абстрактный класс "Животное"
// вид животного // что ест
};
cl {
};
cl
{
ass Tiger: public Animal public:
virtual string kind() const
{ return "Tiger"; }
class TigerFood: public Food
{ public:
virtual string typeFood() { return "Tiger food"; }
};
virtual TigerFood& eats() { return tiger; } private:
TigerFood tiger;
ass Dog: public Animal public:
virtual string kind() const
{ return "Dog"; }
class DogFood: public Food
{ public:
virtual string typeFobdQ { return "Dog food"; }
}:
virtual DogFood& eats()
{ return dog;
}
private:
DogFood dog;
// Тигр - конкретный вид животного
// идентификация вида
// пища тигра
const // идентификация пищи тигра
// ест пищу тигра
// объект - пища тигра
// Собака - конкретный вид животного
// идентификация вида
// пища собаки
const // идентификация пищи собаки
// ест пищу собаки
// объект - пища собаки
};
in {
//
//
//
t main() Tiger t; Dog d; Animal& pt = t; Animal& pd = d; Animal *p[] = { &t. &d };
виртуальный вызов по ссылке cout << pt.eats().typeFoodO << endl; cout << pd.eats().typeFoodO << endl;
виртуальный вызов по указателю cout << р[0]->eats().typeFoodO << endl; cout << p[l]->eats().typeFoodO << endl;
присвоение измененного типа Tiger: :TigerFood tf = t.eatsQ; Dog::DogFood df = d.eatsO; eturn 0;
// объекты-животные
// подстановка ссылок
// подстановка указателей
}
Сначала определены два абстрактных базовых класса: Food и Animal. Эти классы определяют для своих наследников минимальные общие интерфейсы (чистые виртуальные функции). Класс Tiger, наследуя от Animal, содержит вложенный класс TigerFood — наследника от Food. Класс TigerFood вложен в Tiger, так как эти классы тесно связаны — тигр есть только свою пищу. Впрочем, исходный абстрактный базовый класс Food можно вложить и в исходный абстрактный класс Animal:
class Animal { public:
class Food // вложенный абстрактный класс
{ public:
virtual string typeFoodO const = 0:
};
virtual string kind() const = 0: virtual Food& eats() = 0:
};
Тогда вложенный класс Tiger Food будет наследником от вложенного абстрактного класса Food.
Метод eats(), который в базовом классе Animal возвращал ссылку на базовый класс Food, переопределен и возвращает ссылку на TigerFood — изменено возвращаемое значение. Аналогично можно изменять и тип возвращаемого указателя.
Точно такое строение имеет и класс Dog. Главная программа демонстрирует правильные вызовы виртуальных функций eats() через ссылки и указатели базового типа.
Трудно переоценить роль абстрактных классов в практике программирования. Они широко применяются в паттернах проектирования [17, 18]. Вся компонентная технология [51, 52] построена на абстрактных классах-интерфейсах. Они нам еще не раз пригодятся. |