Проблемы с одиночным наследованием

Давайте продолжим работу над программой о животных и предположим, что в ней теперь используется два класса, произведенных от какого-то общего класса. Один — Bird, посвященный птицам, а другой — Mammals, посвященный млекопитающим. Класс Bird содержит функцию-член Fly(), задающую возможность полета. Класс Mammals разбит на ряд подклассов, включая класс лошадей — Horse. Класс содержит две функции- члена — Whinny() и Gallop(), объявляющих ржание и бег галопом соответственно.

Но внезапно у вас возникает желание создать новый весьма интересный мифический объект — крылатого Пегаса (Pegasus), который был бы чем-то вроде гибрида между Horse и Bird. Сразу предупредим, что, используя только одиночное наследование, вам сложно будет справиться с этой задачей.

Если объявить объект Pegasus как член класса Bird, то для него станут недоступными функции Whinny() и Gallop(). Если Pegasus объявить как объект класса Horse, то ему станет недоступной функция Fly().

Первое решение может состоять в том, чтобы скопировать метод Fly() в класс Horse, после чего в этом классе создать объект Pegasus. При этом оба класса (Bird и Horse) будут содержать один и тот же метод Fly(), и при изменении метода в одном классе нужно будет не забыть внести соответствующие изменения в другом классе. Хорошо, если таких классов будет только два. Если вам придется вносить изменения в программу через некоторое время после ее создания, будет сложно вспомнить, в каких еще классах представлен этот метод.

Когда вы захотите создать списки объектов классов Bird и Horse, перед вами возникнет еще одна проблема. Хотелось бы, чтобы объект Pegasus был представлен в обоих списках, но в данном случае это невозможно.

Для решения возникшей проблемы можно использовать несколько подходов. Haпример, можно переименовать слишком "лошадиный" метод Gallop() в более обтекаемый Move(), после чего заместить этот метод в объекте Pegasus таким образом, чтобы он выполнял функцию метода Fly(). В других объектах класса Horse метод Move() будет выполняться так же, как раньше выполнялся метод Gallop(). Для объекта Pegasus можно даже определить, что короткие дистанции он должен преодолевать методом Gallop(), а длинные — методом Fly():

Pegasus::Move(long distance)

{

if (distance > veryFar)

fly(distance);

else

gallop(distance);

}

Но и этот подход имеет ряд ограничений, поскольку объект уже не сможет летать на короткие дистанции и бегать на длинные. Может быть, все же просто перенести метод Fly() в класс Horse, как показано в листинге 13.1? Проблема состоит в том, что лошади, в большинстве своем, летать не умеют, поэтому во всех объектах этого класса, за исключением объекта Pegasus, данный метод не должен ничего выполнять.

Листинг 13.1. Умеют ли лошади летать...

1: // Листинг 13.1. Умеют ли лошади летать...

2: // Фильтрация метода Fly() в классе Horse

3:

4: #include <iostream.h>

5:

6: class Horse

7: {

8: public:

9: void Gallop(){ cout << "Galloping...\n"; }

10: virtual void Fly() { cout << "Horses can't fly.\n"; }

11: private:

12: int itsAge;

13: };

14:

15: class Pegasus : public Horse

16: {

17: public:

18: virtual void Fly() { cout << "I can fly! I can fly! I can fly!\n"; }

19: };

20:

21: const int NumberHorses = 5;

22: int main()

23: {

24: Horse* Ranch[NumberHorses];

25: Horse* pHorse;

26: int choice,i;

27: for (i=0; i<NumberHorses; i++)

28: {

29: cout << "(1)Horse (2)Pegasus: ";

30: cin >> choice;

31: if (choice == 2)

32: pHorse = new Pegasus;

33: else

34: pHorse = new Horse;

35: Ranch[i] = pHorse;

36: }

37: cout << "\n";

38: for (i=0; i<NumberHorses; i++)

39: {

40: Ranch[i]->Fly();

41: delete Ranch[i];

42: }

43: return 0;

44: }

Результат:

(1)Horse (2)Pegasus; 1

(1)Horse (2)Pegasus: 2

(1)Horse (2)Pegasus: 1

(1)Horse (2)Pegasus: 2

(1)Horse (2)Pegasus: 1

Horses can't fly.

I can fly! I can fly! I can fly!

Horses can't fly.

I can fly! I can fly! I can fly!

Horses can't fly.

Анализ: Безусловно, эта программа будет работать ценой добавления в класс Horse редко используемого метода Fly(). Это произошло в строке 10. Для объектов данного класса этот метод констатирует факт, что лошади летать не умеют. И только для объекта Pegasus метод замещается в строке 18 таким образом, что при вызове его объект заявляет, что умеет летать.

В строке 24 используется массив указателей на объекты класса Horse, с помощью которого метод Fly() вызывается для разных объектов класса. В зависимости от того, для какого из объектов в данный момент вызывается метод, программа выводит на экран разные сообщения.

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

Наши рекомендации