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

Top.Mail.Ru

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

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


Содержание


Шаг шестой. Добавление мультиметода

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

Добавление мультиметода при процедурном подходе

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


  // multimethod.c
  // Обработчик специализации для двух прямоугольников
  void MMRR(Rectangle* r1, Rectangle* r2, FILE* ofst) {
    fprintf(ofst, "Rectangle - Rectangle Combination\n");
  }
  // Обработчик специализации для прямоугольника и треугольника
  void MMRT(Rectangle* r1, Triangle* t2, FILE* ofst) {
    fprintf(ofst, "Rectangle - Triangle Combination\n");
  }
  // Обработчик специализации для треугольника и прямоугольника
  void MMTR(Triangle* t1, Rectangle* r2, FILE* ofst) {
    fprintf(ofst, "Triangle - Rectangle Combination\n");
  }
  // Обработчик специализации для двух треугольников
  void MMTT(Triangle* t1, Triangle* t2, FILE* ofst) {
    fprintf(ofst, "Triangle - Triangle Combination\n");
  }
  //------------------------------------------------------------------------------
  void Multimethod(Figure* f1, Figure* f2, FILE* ofst) {
    switch(f1->k) {
      case RECTANGLE:
        switch(f2->k) {
          case RECTANGLE:
            MMRR((Rectangle*)f1, (Rectangle*)f2, ofst);
            break;
          case TRIANGLE:
            MMRT((Rectangle*)f1, (Triangle*)f2, ofst);
            break;
          default:
            fprintf(ofst,
                  "1st is RECTANGLE. Incorrect key of figure 2 = %d\n", f2->k);
        }
        break;
      case TRIANGLE:
        switch(f2->k) {
          case RECTANGLE:
            MMTR((Triangle*)f1, (Rectangle*)f2, ofst);
            break;
          case TRIANGLE:
            MMTT((Triangle*)f1, (Triangle*)f2, ofst);
            break;
          default:
            fprintf(ofst,
                  "1st is TRIANGLE. Incorrect key of figure 2 = %d\n", f2->k);
        }
        break;
      default:
        fprintf(ofst, "Incorrect key of figure 1 = %d\n", f1->k);
    }
  }

Вложенный цикл при обходе контейнера обеспечивает получение всех комбинаций мультиметода.


  // container-mm-out.c - вывод пар элементов контейнера
  void ContainerMultimethodOut(Container *c, FILE* ofst) {
    fprintf(ofst, "Container contains %d elements.\n", c->len);
    for(int i = 0; i < c->len; i++) {
      for(int j = 0; j < c->len; j++) {
        fprintf(ofst, "[%d,%d]: " , i, j);
        Multimethod(c->cont[i], c->cont[j], ofst);
      }
    }
  }

И в качестве вишенки вставка вызова функции обхода контейнера в клиентский код.


  fprintf(ofst, "Multimethod out.\n");
  ContainerMultimethodOut(&c, ofst);

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

Шаг 6. Добавление мультиметода при процедурном подходе

Добавление мультиметода при объектно-ориентированном подходе

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

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


  // figure.h - класс, обобщающий фигуры.
  class Figure {
    public:
    // идентификация, порождение и ввод фигуры из потока
    virtual void In(std::ifstream &ifst) = 0;  // ввод данных из потока
    virtual void Out(std::ofstream &ofst) = 0;     // вывод данных в файл
    virtual void Out() = 0;                        // вывод данных в консоль
    virtual std::string Multimethod(Figure& fig2) = 0; // мультиметод
    virtual std::string FirstRectangle(Rectangle& rect) = 0;
    virtual std::string FirstTriangle(Triangle& trian) = 0;
  };

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

  // triangle.h - треугольник
  class Triangle: public Figure {
    int a, b, c; // стороны
    public:
    // переопределяем интерфейс класса
    virtual void In(std::ifstream &ifst);  // ввод данных из потока
    virtual void Out(std::ofstream &ofst);    // вывод данных в файл
    virtual void Out();                       // вывод данных в cout
    virtual std::string Multimethod(Figure& fig2); // мультиметод
    virtual std::string FirstRectangle(Rectangle& rect);
    virtual std::string FirstTriangle(Triangle& trian);
    Triangle(): a{0}, b{0}, c{0} {} // создание без инициализации.
  };

  // multimethod.cpp - реализация мультиметода
  //------------------------------------------------------------------------------
  // Мультиметод, определяющий вход в прямоугольник
  std::string  Rectangle::Multimethod(Figure& fig2) {
    return fig2.FirstRectangle(reinterpret_cast<Rectangle&>(*this));
  }
  // Обработчик двух прямоугольников
  std::string  Rectangle::FirstRectangle(Rectangle& rect) {
    return "Rectangle - Rectangle Combination\n";
  }
  // Обработчик треугольника и прямоугольника
  std::string  Rectangle::FirstTriangle(Triangle& trian) {
    return "Triangle -  Rectangle Combination\n";
  }
  //------------------------------------------------------------------------------
  // Мультиметод, определяющий вход в треугольник
  std::string Triangle::Multimethod(Figure& fig2) {
    return fig2.FirstTriangle(reinterpret_cast<Triangle&>(*this));
  }
  // Обработчик прямоугольника и треугольника
  std::string Triangle::FirstRectangle(Rectangle& rect) {
    return "Rectangle - Triangle Combination\n";
  }
  // Обработчик двух треугольников
  std::string Triangle::FirstTriangle(Triangle& trian) {
    return "Triangle - Triangle Combination\n";
  }

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


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

  // container-mm-out.cpp - вывод содержимого контейнера
  void Container::MultimethodOut(std::ofstream &ofst) {
    ofst << "Container contents " << len << " elements.\n";
    for(int i = 0; i < len; i++) {
      for(int j = 0; j < len; j++) {
        ofst << "[" << i << "," << j << "]:" <<
        cont[i]->Multimethod(*cont[j]);
      }
    }
  }

Вызов этого метода осуществляется в клиентском коде.


  ofst << "Multimethod out.\n";
  c.MultimethodOut(ofst);

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

Шаг 6. Добавление мультиметода при ОО подходе

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

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


  // multimethod.c
  // Обобщающая функция, задающая абстрактный мультиметод
  void Multimethod<Figure* f1, Figure* f2>(FILE* ofst) {} //= 0;
  // Обработчик специализации для двух прямоугольников
  void Multimethod<Figure.rect* r1, Figure.rect* r2>(FILE* ofst) {
    fprintf(ofst, "Rectangle - Rectangle Combination\n");
  }
  // Обработчик специализации для прямоугольника и треугольника
  void Multimethod<Figure.rect* r1, Figure.trian* t2>(FILE* ofst) {
    fprintf(ofst, "Rectangle - Triangle Combination\n");
  }
  // Обработчик специализации для треугольника и прямоугольника
  void Multimethod<Figure.trian* t1, Figure.rect* r2>(FILE* ofst) {
    fprintf(ofst, "Triangle - Rectangle Combination\n");
  }
  // Обработчик специализации для двух треугольников
  void Multimethod<Figure.trian* t1, Figure.trian* t2>(FILE* ofst) {
    fprintf(ofst, "Triangle - Triangle Combination\n");
  }

Дополнительная внешняя функция используется для вывода пар элементов, размещенных в контейнере.

// container-mm-out.c - вывод элементов через вызов мультиметода
void ContainerMultimethodOut(Container *c, FILE* ofst) {
  fprintf(ofst, "Container contains %d elements.\n", c->len);
  for(int i = 0; i < c->len; i++) {
    for(int j = 0; j < c->len; j++) {
      fprintf(ofst, "[%d,%d]: " , i, j);
      Multimethod<c->cont[i], c->cont[j]>(ofst);
    }
  }
}

Ее вызов изменяет код клиента.


  fprintf(ofst, "Multimethod out.\n");
  ContainerMultimethodOut(&c, ofst);

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

Шаг 6. Добавление мультиметода при ПП подходе

Выводы

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


Содержание