Бьерн Страуструп - Язык программирования С++. Главы 11-13
Страница 31. Иерархии классов



12.2.2 Иерархии классов

Рассмотрим моделирование транспортного потока в городе, цель которого
достаточно точно определить время, требующееся, чтобы аварийные движущиеся
средства достигли пункта назначения. Очевидно, нам надо иметь
представления легковых и грузовых машин, машин скорой помощи,
всевозможных пожарных и полицейских машин, автобусов и т.п.
Поскольку всякое понятие реального мира не существует изолированно,
а соединено  многочисленными связями с другими понятиями,
возникает такое отношение как наследование. Не разобравшись в понятиях
и их взаимных связях,  мы не в состоянии постичь никакое отдельное
понятие. Также и модель, если не отражает отношения между
понятиями, не может адекватно представлять сами понятия. Итак, в
нашей программе нужны классы для представления понятий, но этого
недостаточно. Нам нужны способы представления отношений между классами.
Наследование является мощным способом прямого представления
иерархических отношений. В нашем примере, мы, по всей видимости,
сочли бы аварийные средства специальными движущимися средствами
и, помимо этого, выделили бы средства, представленные легковыми и
грузовыми машинами. Тогда иерархия классов приобрела бы такой вид:
     движущееся средство
 легковая машина  аварийное средство грузовая машина
полицейская машина машина скорой помощи пожарная машина
машина с выдвижной лестницей
Здесь класс Emergency представляет всю информацию, необходимую для
моделирования аварийных движущихся средств, например: аварийная
машина может нарушать некоторые правила движения, она имеет
приоритет на перекрестках, находится под контролем диспетчера
и т.д.
На С++ это можно задать так:

    class Vehicle { /*...*/ };
    class Emergency { /*   */ };
    class Car : public Vehicle { /*...*/ };
    class Truck : public Vehicle { /*...*/ };
    class Police_car : public Car , public Emergency {
        //...
    };
    class Ambulance : public Car , public Emergency {
        //...
    };
    class Fire_engine : public Truck , Emergency {
        //...
    };
    class Hook_and_ladder : public Fire_engine {
        //...
    };

Наследование - это отношение самого высокого порядка, которое прямо
представляется в С++ и используется преимущественно на ранних
этапах проектирования. Часто возникает проблема выбора: использовать
наследование для представления отношения или предпочесть ему
принадлежность. Рассмотрим другое определение понятия аварийного
средства: движущееся средство считается аварийным, если оно
несет соответствующий световой сигнал. Это позволит упростить
иерархию классов, заменив класс Emergency на член класса
Vehicle:
      движущееся средство (Vehicle {eptr})
 легковая машина (Car) грузовая машина (Truck)
полицейская машина (Police_car) машина скорой помощи (Ambulance)
  пожарная машина (Fire_engine)
  машина с выдвижной лестницей (Hook_and_ladder)
Теперь класс Emergency используется просто как член в тех классах,
которые представляют аварийные движущиеся средства:

     class Emergency { /*...*/ };
     class Vehicle { public: Emergency* eptr;  /*...*/ };
     class Car : public Vehicle { /*...*/ };
     class Truck : public Vehicle { /*...*/ };
     class Police_car : public Car { /*...*/ };
     class Ambulance : public Car { /*...*/ };
     class Fire_engine : public Truck { /*...*/ };
     class Hook_and_ladder : public Fire_engine { /*...*/ };

Здесь движущееся средство считается аварийным, если Vehicle::eptr
не равно нулю. "Простые" легковые и грузовые машины инициализируются
Vehicle::eptr равным нулю, а для других Vehicle::eptr должно быть
установлено в ненулевое значение, например:

     Car::Car()     // конструктор Car
     {
        eptr = 0;
     }
     Police_car::Police_car()   // конструктор Police_car
     {
        eptr = new Emergency;
     }

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

     void f(Vehicle* p)
     {
        delete p->eptr;
        p->eptr = 0;   // больше нет аварийного движущегося средства

        //...

        p->eptr = new Emergency;   // оно появилось снова
     }

Так какой же вариант иерархии классов лучше? В общем случае ответ такой:
"Лучшей является программа, которая наиболее непосредственно отражает
реальный мир". Иными словами, при выборе модели мы должны стремиться
к большей ее"реальности", но с учетом неизбежных ограничений,
накладываемых требованиями простоты и эффективности. Поэтому,
несмотря на простоту преобразования обычного движущегося средства в
аварийное, второе решение представляется непрактичным.
Пожарные машины и машины скорой помощи - это
движущиеся средства специального назначения со специально
подготовленным персоналом, они действуют под управлением команд
диспетчера, требующих специального оборудования для связи. Такое
положение означает, что принадлежность к аварийным движущимся средствам -
это базовое понятие, которое для улучшения контроля типов и
применения различных программных средств должно быть прямо
представлено в программе. Если бы мы моделировали ситуацию, в которой
назначение движущихся средств не столь определенно,
скажем, ситуацию, в которой частный транспорт периодически используется
для доставки специального персонала к месту происшествия, а связь
обеспечивается с помощью портативных приемников, тогда мог бы
оказаться подходящим и другой способ моделирования системы.
    Для тех, кто считает пример моделирования движения транспорта
экзотичным, имеет смысл сказать, что в процессе проектирования
почти постоянно возникает подобный выбор между наследованием
и принадлежностью. Аналогичный пример есть в $$12.2.5, где
описывается свиток (scrollbar) - прокручивание информации в окне.

 
« Предыдущая статья   Следующая статья »