|
Рассмотрим моделирование транспортного потока в городе, цель которого достаточно точно определить время, требующееся, чтобы аварийные движущиеся средства достигли пункта назначения. Очевидно, нам надо иметь представления легковых и грузовых машин, машин скорой помощи, всевозможных пожарных и полицейских машин, автобусов и т.п. Поскольку всякое понятие реального мира не существует изолированно, а соединено многочисленными связями с другими понятиями, возникает такое отношение как наследование. Не разобравшись в понятиях и их взаимных связях, мы не в состоянии постичь никакое отдельное понятие. Также и модель, если не отражает отношения между понятиями, не может адекватно представлять сами понятия. Итак, в нашей программе нужны классы для представления понятий, но этого недостаточно. Нам нужны способы представления отношений между классами. Наследование является мощным способом прямого представления иерархических отношений. В нашем примере, мы, по всей видимости, сочли бы аварийные средства специальными движущимися средствами и, помимо этого, выделили бы средства, представленные легковыми и грузовыми машинами. Тогда иерархия классов приобрела бы такой вид:
Здесь класс 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:
Теперь класс 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) - прокручивание информации в окне.