SoftCraft
разноликое программирование

Top.Mail.Ru

Динамический полиморфизм и эволюция

© 2025
Александр Легалов


Содержание


Шаг пятый. Обработка только одной альтернативы

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

Одна альтернатива и процедурный подход

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


  // figure-rectangle-only-out.c - вывод только прямоугольника
  _Bool FigureRectangleOnlyOut(Figure* f, FILE* ofst) {
    switch (f->k) {
      case RECTANGLE:
        RectangleOut(&(f->r), ofst);
        return 1;
      default:
        return 0;
    }
  }

  // container-rectangles-out.c - вывод только прямоугольников
  void ContainerRectanglesOnlyOut(Container* c, FILE* ofst)
  {
    int rectCount = 0;
    for (int i = 0; i < c->len; i++) {
      if(FigureRectangleOnlyOut(c->cont[i], ofst)) {
        ++rectCount;
      }
    }
    fprintf(ofst, "Container contains %d rectangles.\n", rectCount);
  }

Вызов функции, осуществляющей обход контейнера для этой ситуации, добавляется в клиентский код.


  fprintf(ofst, "Only rectangles.\n");
  ContainerRectanglesOnlyOut(&c, ofst);

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

Шаг 5. Обработка одной альтернативы при процедурном подходе

Одна альтернатива и объектно-ориентированный подход

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


  // figure.h- обобщающенная фигура с методом вывода одной альтернативы
  class Figure {
    public:
    virtual void In(std::ifstream &ifst) = 0;      // ввод
    virtual void Out(std::ofstream &ofst) = 0;     // вывод
    // Метод, пропускающий вывод не прямоугольников
    virtual void RectOnlyOut(std::ofstream &ofst) {}
  };

  // rectangle.cpp - прямоугольник
  class Rectangle: public Figure {
    int x, y; // ширина, высота
    public:
    // переопределяем интерфейс класса
    virtual void In(std::ifstream &ifst);  // ввод данных из потока
    virtual void Out(std::ofstream &ofst);     // вывод данных в файл
    // Вывод только прямоугольника
    virtual void RectOnlyOut(std::ofstream &ofst);
    Rectangle(): x{0}, y{0} {} // создание без инициализации.
  };

  // container.h - контейнер
  class Container {
    enum {max_len = 100}; // максимальная длина
    int len; // текущая длина
    Figure *cont[max_len];
    public:
    void In(std::ifstream &ifst);     // ввод фигур в котнейнер
    void Out(std::ofstream &ofst);    // вывод фигур
    // Вывод из содержимого контейнера только прямоугольников
    void RectOnlyOut(std::ofstream &ofst);
    void Clear();  // очистка контейнера от фигур
    Container();    // инициализация контейнера
    ~Container() {Clear();} // утилизация контейнера перед уничтожением
  };

Реализация методов в плюсах может быть вынесена, что позволяет уменьшить число изменений.


  // rectangle-only-out.cpp - вывод только прямоугольника
  void Rectangle::RectOnlyOut(std::ofstream &ofst) {
    Out(ofst);
  }

  // container-rect-only-out.cpp - вывод из контейнера только прямоугольников
  void Container::RectOnlyOut(std::ofstream &ofst) {
    ofst << "Container contents " << len << " elements.\n";
    for(int i = 0; i < len; i++) {
      ofst << i << ": ";
      cont[i]->RectOnlyOut(ofst);
    }
  }

Как и предыдущем случае, клиент расширяется новой функциональностью.


  ...
  ofst << "Only rectangles.\n";
  c.RectOnlyOut(ofst);
  ...

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

Шаг 5. Обработка одной альтернативы при ОО подходе

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

Одна альтернатива и процедурно-параметрический подход

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

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


  // is-figure-rectangle.c - проверка, является ли фигура прямоугольником
  _Bool isFigureRectangle(Figure * f) {
    struct Figure.rect figRect;
    if(spec_index_cmp(&figRect, f) >= 0) {
      return 1;
    }
    return 0;
  }

Для проверки используется временная специализированная переменна, индекс специализации которой сравнивается с индексом фигуры, размещенной в контейнере, в функции spec_index_cmp. Если они совпадают, то формируется нулевое или положительное значение этого индекса. В противном случае результат будет равен -1. Полученный результат преобразуется в булевскую переменную, на основании которой функция вывода только прямоугольников выполняет соответствующее действие.


  // Вывод прямоугольников из контейнера в указанный поток
  void ContainerRectangleOnlyOut(Container *c, FILE* ofst) {
    int rectCount = 0;
    for(int i = 0; i < c->len; i++) {
      if(isFigureRectangle<c->cont[i]>()) {
        FigureOut<c->cont[i]>(ofst);
        ++rectCount;
      }
    }
    fprintf(ofst, "Container contains %d rectangles.\n", rectCount);
  }

В представленном коде можно было непосредственно вызвать функцию вывода прямоугольника - основы специализации. Но было лениво писать приведение типов. Клиентский код ничем не отличается от кода процедурной программы.

На схеме демонстрируются варианты решений как с использованием динамическог полиморфизма, так и с применением сравнения типов.

Шаг 5. Добавление обработки только одной альтернативы при ПП подходе

Выводы

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


Содержание