Бьерн Страуструп - Абстракция данных в языке С++ - Производные классы

ОГЛАВЛЕНИЕ

 

        Производные классы

    Рассмотрим проект системы для управления выводом
  геометрических фигур на экран терминала. Привлекательный подход
  заключается в трактовке каждой фигуры как объекта, который можно
  запросить для выполнения определенных действий типа "вращаться" и
  "изменить цвет". Каждый объект будет интерпретировать такой
  запрос в соответствии со своим типом. Например, алгоритм вращения
  круга несомненно будет отличаться (будет проще) алгоритма
  вращения треугольника. Что действительно нужно, то это единый
  интерфейс к разнообразию сосуществующих реализаций. Не следует
  предполагать, что разные виды фигур будут иметь похожее
  представление. По сложности они будут отличаться в широких
  пределах,и будет жаль, если не удастся использовать естественную
  простоту базовых фигур типа круга или треугольникаа из-за того,
  что необходимо поддерживать сложные фигуры типа "мышь" или
  "Британские острова".

    Общий подход заключается в предоставлении классу s h a p e
  возможности определить общие свойства фигур, в частности,
  "стандартный интерфейс". Например:

      class shape { 
            point center;
            int color;
            shape *next;
            static shape *shape_chain;
            ...
        public:
            void move (point to) { center = to; draw(); }
            point where() {return center; }
            virtual void rotate(int);
            virtual void draw();
            ...
      };

    Функции, которые не могут быть реализованы без знания
  специфических свойств s h a p e , объявляются виртуальными
  (v i r t u a l ). Ожидается, что виртуальные функции будут
  определены позже. На этой стадии известен только их тип; этого
  достаточно, однако, для контроля правильности их вызова.

    Класс, определяющий конкретную фигуру, может быть определен как:

      class circle: public  shape { 
            float radius;
        public:
            void rotate(int angle) {}
            void draw();
            ...
      };

    Здесь определено, что класс s h a p e является классом
  c i r c l e, так как он имеет в своем составе всех членов класса
  s h a p e в дополнение к своим собственным. Говорят, что класс
  c i r c i e является производным от его "базового класса"
  s h a p e . Объекты типа c i r c l l e могут быть теперь описаны
  и использованы:

      circle cl; 
      shape *sh;
      point p (100, 30);
      cl.draw();
      cl.move(p);
      sh = *cl;

    На самом деле функция, вызываемая обращением к c l . d r a w()
  есть c i r c l e . d r a w (), так как тип c i r c l e
  не определяет собственной функции m o v e (), то функция,
  вызываемая обращением к c l . m o v e ( p ), есть
  s h a p e . m o v e, так как класс c i r c l e наследует ее класс

  опять же есть c i r c l e :: d r a w (), несмотря
  на то,а что нельзя найти никакой ссылки на класс c i r c l e
  в описанииа класса s h a p e . Виртуальнаяа функция
  переопределяется, если класс является производным от другого
  класса. Каждый объект класса, включающий виртуальные функции,
  содержит индикатор типа. Это дает возможность компилятору
  находить подходящую виртуальную функцию для каждого вызова, даже
  если тип объекта неизвестен во время компиляции. Вызов
  виртуальной функции - единственный путь использования скрытого в
  классе индикатора типа (класс без виртуальных функций не имеет
  такого индикатора).

    Фигуры также могут предоставлять возможности, которые могут
  быть использованы только, если программист знает их конкретный
  тип. Например:

      class clock_face :  public circle { 
            line hour_hand, minute_hand;
        public:
            void draw();
            void rotate)int);
            void set(int, int);
            void advance(int);
            ...
      }

    Время, которое показывают часы, может быть установлено
  посредством функции s e t ( ) на конкретное значение, также
  может быть переведено посредством функции a d v a n c e ( ) на
  определенное число минут. Функция d r a w ( ) в классе
  c l o c k _ f a c e скрывает c i r c l e :: d r a w ( ) ,
  поэтому последняя должна быть вызвана по своему полному имени.
  Например:

      void clock_face.draw()  { 
            circle::draw();
            hour_hand.draw();
            minute_hand.draw();
      } 

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