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