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

Top.Mail.Ru

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

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


Содержание


Шаг четвертый. Добавление общих данных

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

Общее поле и процедурное обобщение

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


  // figure.h - обобщенная фигура с добавлением цвета
  typedef enum key { RECTANGLE, TRIANGLE } key;
  typedef struct Figure {
    key k; // ключ
    unsigned int color; // цвет фигуры (добавленное поле)
    // используемые альтернативы
    union { // используем простейшую реализацию
      Rectangle r;
      Triangle t;
    };
  } Figure;

  // figure-color-in.c - ввод цвета фигуры
  void ColorIn(Figure *f, FILE* ifst) {
    fscanf(ifst, "%x", &(f->color));
  }

  // figure-color-out.c - вывод цвета фигуры
  void ColorOut(Figure *f, FILE *ofst) {
    fprintf(ofst, "    Color is %#0.6X\n", f->color);
  }

  // figure-in.c - измененный ввод цветной фигуры
  Figure* FigureIn(FILE* ifst) {
    Figure *pf;
    int k;
    fscanf(ifst, "%d", &(k));
    switch(k) {
      case 1:
        pf = malloc(sizeof(Figure));
        pf->k = RECTANGLE;
        RectangleIn(&(pf->r), ifst);
        ColorIn(pf, ifst);
      return pf;
      case 2:
        pf = malloc(sizeof(Figure));
        pf->k = TRIANGLE;
        TriangleIn(&(pf->t), ifst);
        ColorIn(pf, ifst);
        return pf;
      default:
        return 0;
    }
  }

  // figure-out.c - измененный вывод цветной фигуры
  void FigureOut(Figure *f, FILE* ofst) {
    switch(f->k) {
      case RECTANGLE:
        RectangleOut(&(f->r), ofst);
        ColorOut(f, ofst);
        break;
      case TRIANGLE:
        TriangleOut(&(f->t), ofst);
        ColorOut(f, ofst);
        break;
      default:
        fprintf(ofst, "Incorrect figure!\n");
    }
  }

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

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

Шаг 4. Добавление общего поля данных при процедурном подходе

Общее поле при объектно-ориентированном подходе

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


  // decorator.h - декоратор
  class Decorator: public Figure {
    unsigned int color; // цвет фигуры
    Figure* fig; // декорируемая фигура
    public:
    // переопределяем интерфейс класса
    virtual void In(std::ifstream &ifst);   // ввод данных из потока
    virtual void Out(std::ofstream &ofst);      // вывод данных в файл
    virtual void Out();                         // вывод данных в cout
    Decorator(Figure* f): color{0}, fig{f} {}
    virtual ~Decorator() {delete fig;}
  };

  // decorator-in.cpp - ввод черз декоратор фигуры и цвета
  void Decorator::In(std::ifstream &ifst) {
    fig->In(ifst); // Ввод параметров подключенной фигуры
    ifst >> std::hex >> color;     // Ввод цвета фигуры
  }

  // decorator-out.cpp - вывод через декоратор фигуры и цвета
  void Decorator::Out(std::ofstream &ofst) {
    if(fig != nullptr) {
      fig->Out(ofst);
    }
    ofst << "    Color is 0x" << std::hex << std::uppercase << color << "\n";
  }

  // figure-factory.cpp - обход фабрик фигур и связывание с декоратором
  Figure* FigureFactory::Create(std::ifstream &ifst) {
    int k;
    ifst >> k;
    for(int i = 0; i < FigureFactory::counter; ++i) {
      Figure* f = factory[i]->Test(k);
      if(f != nullptr) {
        // Добавление декоратора для цвета фигуры
        auto d = new Decorator(f);
        d->In(ifst);
        return d;
      }
    }
    return nullptr;
  }

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

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

Шаг 4. Добавление общего поля при ОО подходе

Общее поле при процедурно-параметрическом подходе

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

Шаг 4. Добавление общего поля при ПП подходе

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

Добавление общего поля в рассматриваемом варианте сводится к формированию "цветных" фигур, построенных на существующих фигурах, выполняющих роль основ специализаций. То есть из формируемого кода выбрасываются ранее созданные специализации: фигура-прямоугольник и фигура-треугольник. Вместо них появляются специализации, построенные по иному принципу, когда цвет обобщает конкретные фигуры.


  // figure-color.h - формирование цветных обобщенных фигур
  typedef struct Color {
    unsigned int color; // цвет фигуры
  } <> Color; // Жесткая специализация декоратора как фигуры

  // Цветные прямоугольник и треугольник
  Color + <rect: Rectangle;>;
  Color + <trian: Triangle;>;

  // Обобщенная цветная фигура
  Figure + < color: Color; > ;

  //------------------------------------------------------------------------------
  // Ввод цветного прямоугольника из файла
  Input + <color_rect: void;>;
  // Ввод цветного треугольника из файла
  Input + <color_trian: void;>;

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

В результате этих манипуляций возможно итеративное наращивание специализаций с формированием монолитных структур данных. Нам достаточно для примера получать переменные, состоящие из двух подтипов, взаимно вложенных в тип Figure:


  Figure.color.rect
  Figure.color.trian

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


  // figure-color-in.c - организация ввода обобщенных цветных фигур
  //------------------------------------------------------------------------------
  // Прототип функции ввода прямоугольника
  void RectangleIn(Rectangle* r, FILE* ifst);
  // Прототип функции ввода треугольника
  void TriangleIn(Triangle* t, FILE* ifst);
  //------------------------------------------------------------------------------
  // Обобщенная функция ввода фигуры с цветом
  void ColoredIn<Color* f>(FILE* ifst) {}
  // Обработчик специализации, осуществлющий ввод прямоугольника с цветом
  void ColoredIn<Color.rect* f>(FILE* ifst) {
    RectangleIn(&(f->@), ifst);
  }
  // Обработчик специализации, осуществлющий ввод треугольника с цветом
  void ColoredIn<Color.trian* f>(FILE* ifst) {
    TriangleIn(&(f->@), ifst);
  }
  //------------------------------------------------------------------------------
  // Ввод цветной (декорируемой) фигуры
  void FigureIn<Figure.color* d>(FILE* ifst) {
    // В начале вводятся параметры подключенной фигуры
    // FigureIn<&(d->@.@)>(ifst);
    struct Color* dfp = (struct Color*)(&d->@);
    ColoredIn<dfp>(ifst);
    // Затем вводится содержимое декоратора (цвет)
    fscanf(ifst, "%x", &(d->@color));
  }
  //------------------------------------------------------------------------------
  // Обобщенная функция создания декорируемой фигуры по признаку из файла
  Figure* CreateDecoratedFigure<Figure* f>(int k) {return NULL;}
  // Обработчик специализации, создающий декорируемый прямоугольник
  Figure* CreateDecoratedFigure<Color.rect* f>(int k) {
    return create_spec(Figure.color.rect);
  }
  // Обработчик специализации, создающий декорируемый прямоугольник
  Figure* CreateDecoratedFigure<Color.trian* f>(int k) {
    return create_spec(Figure.color.trian);
  }
  //------------------------------------------------------------------------------
  // Создание цветного прямоугольника по признаку из файла
  Figure* FigureCreateUseInputTag<Input.color_rect *pFig>(int k) {
    if(k == 1) {
      // printf("k = %d: It is Color Rectangle\n", k);
      return create_spec(Figure.color.rect);
    } else {
      return NULL;
    }
  }
  //------------------------------------------------------------------------------
  // Создание цветного треугольника по признаку из файла
  Figure* FigureCreateUseInputTag<Input.color_trian *pFig>(int k) {
    if(k == 2) {
      // printf("k = %d: It is Color Triangle\n", k);
      return create_spec(Figure.color.trian);
    } else {
      return NULL;
    }
  }

Аналогичные цепочки диспетчеризации используются и для функций вывода.


  // figure-color-out.c - организация вывода обобщенных цветных фигур
  //------------------------------------------------------------------------------
  // Прототип функции вывода прямоугольника
  void RectangleOut(Rectangle *r, FILE* ofst);
  // Прототип функции вывода треугольника
  void TriangleOut(Triangle *t, FILE* ofst);
  //------------------------------------------------------------------------------
  // Обобщенная функция вывода фигуры с цветом
  void ColoredOut<Color* f>(FILE* ofst) {}
  // Обработчик специализации, осуществлющий вывод прямоугольника с цветом
  void ColoredOut<Color.rect* f>(FILE* ofst) {
    RectangleOut(&(f->@), ofst);
  }
  // Обработчик специализации, осуществлющий вывод треугольника с цветом
  void ColoredOut<Color.trian* f>(FILE* ofst) {
    TriangleOut(&(f->@), ofst);
  }
  //------------------------------------------------------------------------------
  // Ввод фигуры совместно с содержимым декоратора
  void FigureOut<Figure.color * d>(FILE* ofst) {
    // В начале выводится содержимое фигуры
    struct Color* dfp = (struct Color*)&d->@;
    ColoredOut<dfp>(ofst);
    // Затем содержимое декоратора
    fprintf(ofst, "    Color is %#0.6X\n", d->@color);
  }

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

Выводы

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


Содержание