|
© 12.04.2026
Александр Легалов
Примеры всех рассмотренных программ (59.3 кб)
Введение
Когда-то давным давно Гради Буч написалБуч Г. Объектно-ориентированное проектирование с примерами применения. - M.: Конкорд, 1992. 519 с., что "Объектная модель позволяет в полной мере использовать выразительные возможности объектных и объектно-ориентированных (ОО) языков программирования", ссылаясь при этом на Б. Страуструпа, который, сказал:
"Не всегда очевидно, как в полной мере использовать преимущества такого языка, как C++. Существенно повысить эффективность и качество кода можно просто за счет использования C++ в качестве "улучшенного C" с элементами абстракции данных. Однако гораздо более значительным достижением является введение иерархии классов в процессе проектирования. Именно это называется OOD и именно здесь преимущества C++ демонстрируются наилучшим образом".
Шли годы, менялось название книги, менялись номера изданий. Но цитата оставалась неизменной. По крайней мере в изданиях на русском языке. Хотя уже изменились не только языки ОО программирования, но и взгляды Б. Страуструпа на язык С++. Появились процедурные языки Go и Rust, которые в настоящее время вовсю используются в ОО проектах, чему способствовала реализация в них динамического полиморфизма на основе статической утиной типизации (СУТ), также обзываемой (в более общем контексте) структурной типизацией. По сути можно говорить, что эти языки, обеспечивая поддержку своих собственных методов динамического полиморфизма, позволяют достаточно просто имитировать ОО стиль. Гораздо проще, чем это делается с использованием мономорфных языков, не поддерживающих динамический полиморфизм. Например, C или Zig, имитация ОО подхода или СУТ с использованием которых является более трудоемкой, хоть и используется иногда на практике. В частности, в языке zig имитация полиморфизма является одним из основных подходов к построению библиотек.
Определенный интерес вызывает то, во что обойдется имитация ООП и СУТ с использованием процедурно-параметрического (ПП) подхода. В целом многократно было показано, что процедурно-параметрический полиморфизм обеспечивает более гибкую эволюционную разработку программ. Но в данном случае речь идет о том, во что, используя эту технику композиции данных и функций, может выразиться создание аналогов объектов, интерфейсов и типажей. Типажи, правда, можно оставить за кадром, ограничившись схожими с ними интерфейсами языка Go.
Для сопоставления имитации с непосредственными реализациями опять же обратимся к замусоленному примеру, в котором идет обработка геометрических фигур, записываемых в тривиальный контейнер. Помимо операций ввода-вывода реализуем вычисление периметров и площадей прямоугольников и треугольников. В данной ситуации речь не идет о гибкости и эволюции. Основная идея связана с имитацией структуры классов и интерфейсов. Точнее то, во что они отобразятся при программировании на нашем PPC. И на сколько это будет трудоемко, удобно, наглядно.
Имитация объектно-ориентированного полиморфизма
Использование ООП задает описание каждой программной единицы в виде класса, на основе которого порождаются объекты, являющиеся экземплярами этого класса. Размещение данных и методов внутри класса обеспечивает непосредственное взаимодействие между ними. В настоящее время поощряется наследование интерфейсов вместо наследования реализации, что для языка C++ выражается в применении на вершине иерархии абстрактных классов. В общем случае не одобряется использование статических методов, во многом близких по семантике к внешним функциям процедурных программ. Но один такой метод я реализую для организации простейшего ввода данных.
Отображение на ПП стиль связано с переходом от классов к обобщенным структурам и построением для них специализаций вместо объектов. Обработка этих переменных осуществляется специализированными функциями, обеспечивающими полиморфизм вместо ОО полиморфизма виртуальных методов классов.
Простая объектно-ориентированная программа
Перед имитацией имеет смысл рассмотреть, во что выльется создание сопоставляемых конструкций при использовании объектно-ориентированного подхода. Это касается построения базового класса, задающего обобщенную фигуру, а также производных классов, определяющих специализации для прямоугольника и треугольника. Класс, задающий упрощенный контейнер, использует одномерный массив указателей на обобщенные фигуры. Ниже представлены соответствующие объявления (проект размещен в подкаталоге "all/01-oop-c++/" прилагаемого архива.).
// figure.h - класс, обобщающий фигуры.
class Figure {
public:
// иденитификация, порождение и ввод фигуры из потока
static Figure* Create(std::ifstream &ifst);
virtual void In(std::ifstream &ifst) = 0; // ввод данных из файла
virtual void Out(std::ofstream &ofst) = 0; // вывод данных в файл
virtual double Perimeter() = 0; // вычисление периметра
virtual double Area() = 0; // вычисление площади
};
// rectangle.h - прямоугольник
class Rectangle: public Figure {
int x, y; // ширина, высота
public:
// переопределяем интерфейс класса
virtual void In(std::ifstream &ifst); // ввод данных из файла
virtual void Out(std::ofstream &ofst); // вывод данных в файл
virtual double Perimeter(); // вычисление периметра
virtual double Area(); // вычисление площади
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 double Perimeter(); // вычисление периметра
virtual double Area(); // вычисление площади
Triangle(): a{0}, b{0}, c{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 PerimeterOut(std::ofstream &ofst); // вывод периметров
void AreaOut(std::ofstream &ofst); // вывод площадей
void Clear(); // очистка контейнера от фигур
Container(); // инициализация контейнера
~Container() {Clear();} // утилизация контейнера перед уничтожением
};
Реализации методов для этих классов вынесены в отдельные единицы компиляции, каждая из которых связана только со своим классом. При этом для фигуры реализуется только статический метод, обеспечивающий упрощенный ввод параметров конкретных фигур из файла.
//------------------------------------------------------------------------------
// figure.cpp - Создание фигуры по ее признаку в файле и полиморфный ввод
Figure* Figure::Create(std::ifstream &ifst) {
Figure *sp;
int k;
ifst >> k;
switch(k) {
case 1:
sp = new Rectangle;
break;
case 2:
sp = new Triangle;
break;
default:
return 0;
}
sp->In(ifst);
return sp;
}
//------------------------------------------------------------------------------
// rectangle.cpp - единица компиляции для методов прямоугольника
// Ввод параметров прямоугольника
void Rectangle::In(std::ifstream &ifst) {
ifst >> x >> y;
}
// Вывод параметров прямоугольника
void Rectangle::Out(std::ofstream &ofst) {
ofst << "It is Rectangle: x = " << x << ", y = " << y << "\n";
}
// Вычисление периметра прямоугольника
double Rectangle::Perimeter() {
return (double)(2*(x + y));
}
// Вычисление площади прямоугольника
double Rectangle::Area() {
return (double)(x * y);
}
//------------------------------------------------------------------------------
// triangle.cpp - единица компиляции для методов треугольника
// Ввод параметров треугольника
void Triangle::In(std::ifstream &ifst) {
ifst >> a >> b >> c;
}
// Вывод параметров треугольника
void Triangle::Out(std::ofstream &ofst) {
ofst << "It is Triangle: a = "
<< a << ", b = " << b
<< ", c = " << c << "\n";
}
// Вычисление периметра треугольника
double Triangle::Perimeter() {
return (double)(a + b + c);
}
// Вычисление площади треугольника
double Triangle::Area() {
auto p = double(a + b + c) / 2.0;
return sqrt(p*(p-a)*(p-b)*(p-c));
}
//------------------------------------------------------------------------------
// contaiiner.cpp - единица компиляции для методов контейнера
// Инициализация контейнера
Container::Container(): len{0} { }
// Очистка контейнера от элементов (освобождение памяти)
void Container::Clear() {
for(int i = 0; i < len; i++) {
delete cont[i];
}
len = 0;
}
// Ввод содержимого контейнера
void Container::In(std::ifstream &ifst) {
while(!ifst.eof()) {
if((cont[len] = Figure::Create(ifst)) != 0) {
len++;
}
}
}
// Вывод содержимого контейнера
void Container::Out(std::ofstream &ofst) {
ofst << "Container contents " << len << " elements.\n";
for(int i = 0; i < len; i++) {
ofst << i << ": ";
cont[i]->Out(ofst);
}
}
// Вывод периметров в файл
void Container::PerimeterOut(std::ofstream &ofst) {
ofst << "Perimeters of figures:\n";
auto sum = 0.0;
for(int i = 0; i < len; i++) {
auto p = cont[i]->Perimeter();
sum += p;
ofst << i << ": " << p << "\n";
}
ofst << "Perimeters sum = " << sum << "\n";
}
// Вывод площадей в файл
void Container::AreaOut(std::ofstream &ofst) {
ofst << "Areas of figures:\n";
auto sum = 0.0;
for(int i = 0; i < len; i++) {
auto a = cont[i]->Area();
sum += a;
ofst << i << ": " << a << "\n";
}
ofst << "Area sum = " << sum << "\n";
}
Главная функция расположена в отдельной единице компиляции и имитирует работу простейшего клиента, реализующего требуемую функциональность.
int main(int argc, char* argv[]) {
if(argc !=3) {
std::cout << "incorrect command line! Wated: command infile outfile\n";
return 1;
}
std::ifstream ifst(argv[1]);
std::ofstream ofst(argv[2]);
Container c;
c.In(ifst);
ofst << "Filled container.\n";
c.Out(ofst);
c.PerimeterOut(ofst);
c.AreaOut(ofst);
c.Clear();
return 0;
}
Процедурно-параметрическая имитация простой ОО программы
В отличие от использования обычного процедурного подхода, когда необходимо имитировать таблицы виртуальных методов, ПП подход позволяет непосредственно использовать свои параметрические таблицы, поддерживающие динамический полиморфизм. Поэтому для имитации класса достаточно описать структуру данных и прототипы функций, осуществляющие его обработку. При этом объявления виртуальных методов "превращаются" в прототипы обобщенных функции, реализация которых описывается в соответствующих единицах компиляции (реализация размещена в подкаталоге "all/02-oop-ppc/" архива).
// figure.h - структура, обобщающая фигуры
typedef struct Figure {} <> Figure;
// иденитификация, порождение и ввод фигуры из потока
Figure* FigureCreate(FILE* ifst);
// Прототип обобщеннай функции ввода фигуры
void FigureIn<Figure *f>(FILE* file);
// Прототип обобщеннай функции вывода фигуры
void FigureOut<Figure *f>(FILE* ofst);
// Прототип обобщенной функции вычисления периметра
double FigurePerimeter<Figure *f>();
// Прототип обобщенной функции вычисления площади
double FigureArea<Figure *f>();
// rectangle.h - прямоугольник как основа специализации
typedef struct Rectangle {
int x, y; // ширина, высота
} Rectangle;
Figure + < rect: Rectangle; >; // фигура - специализированный прямоугольник
// triangle.h - треугольник
typedef struct Triangle {
int a, b, c; // стороны треугольника
} Triangle;
Figure + < trian: Triangle; >; // фигура - специализированный треугольник
enum {max_len = 100}; // максимальная длина
// container.h - простейший контейнер на основе одномерного массива
typedef struct Container {
int len; // текущая длина
struct Figure *cont[max_len];
} Container;
void ContainerInit(Container *c); // инициализация контейнера
void ContainerClear(Container *c); // очистка контейнера от элементов
void ContainerIn(Container* c, FILE* ifst); // ввод содержимого контейнера
void ContainerOut(Container *c, FILE* ofst); // вывод содержимого контейнера
void ContainerPerimeterOut(Container *c, FILE* ofst); // вывод периметров
void ContainerAreaOut(Container *c, FILE* ofst); // вывод площадей фигур
Прототипы для обработчиков специализаций в данном случае не описываются, так как интерфейс доступа к ним определяется прототипами обобщенных функций.
Аналогичным образом для имитации методов можно вынести реализации функций в соответствующие единицы компиляции. В случае сложной обработке основ специализаций можно сделать для каждой из них свои обработчики, что представлено ниже для прямоугольников и треугольников.
//------------------------------------------------------------------------------
// figures-input.c - ввод параметров одной из фигур из файла
// имитирует статический метод фигуры
Figure* FigureCreate(FILE* ifst) {
Figure *sp;
int k = 0;
fscanf(ifst, "%d", &(k));
switch(k) {
case 1:
sp = create_spec(Figure.rect);
break;
case 2:
sp = create_spec(Figure.trian);
break;
default:
return 0;
}
FigureIn<sp>(ifst);
return sp;
}
//------------------------------------------------------------------------------
// figure.c - абстрактные обобщенные фукнции, имитирующие чистые методы
// Обобщающая функция для ввода параметров фигуры
void FigureIn<Figure *f>(FILE* file) {} //= 0;
// Обобщающая функция для вывода параметров фигуры
void FigureOut<Figure *f>(FILE* file) {} //= 0;
// Обобщающая функция для периметра
double FigurePerimeter<Figure *f>() {return 0.0;} //= 0;
// Обобщающая функция для площади
double FigureArea<Figure *f>() {return 0.0;} //= 0;
//------------------------------------------------------------------------------
// rectangle.c - имитация методов прямоугольника
//==============================================================================
// Обработчики основы для прямоугольника
//==============================================================================
// Ввод параметров прямоугольника из файла
void RectangleIn(Rectangle *r, FILE* ifst) {
fscanf(ifst, "%d", &(r->x));
fscanf(ifst, "%d", &(r->y));
}
// Вывод прямоугольника
void RectangleOut(Rectangle *r, FILE* ofst) {
fprintf(ofst, "It is Rectangle: x = %d, y = %d\n", r->x, r->y);
}
// Вычисление периметра прямоугольника
double RectanglePerimeter(Rectangle *r) {
return (double)(2*(r->x + r->y));
}
// Вычисление площади прямоугольника
double RectangleArea(Rectangle *r) {
return (double)(r->x * r->y);
}
//==============================================================================
// Обработчики специализаций для прямоугольника
//==============================================================================
// Ввод прямоугольника как фигуры
void FigureIn<Figure.rect *f>(FILE* ifst) {
RectangleIn(&(f->@), ifst);
}
// Вывод прямоугольника как фигуры
void FigureOut<Figure.rect *f>(FILE* ofst) {
RectangleOut(&(f->@), ofst);
}
// Периметр прямоугольника как фигуры
double FigurePerimeter<Figure.rect *f>() {
return RectanglePerimeter(&(f->@));
// return (double)(2*(f->@x + f->@y));
}
// Площадь прямоугольника как фигуры
double FigureArea<Figure.rect *f>() {
return RectangleArea(&(f->@));
}
//------------------------------------------------------------------------------
// triangle.c - имитация методов треугольника
//==============================================================================
// Обработчики основы для треугольника
//==============================================================================
// Ввод параметров треугольника из файла
void TriangleIn(Triangle *t, FILE* ifst) {
fscanf(ifst, "%d", &(t->a));
fscanf(ifst, "%d", &(t->b));
fscanf(ifst, "%d", &(t->c));
}
// Вывод треугольника
void TriangleOut(Triangle *t, FILE *ofst) {
fprintf(ofst, "It is Triangle: a = %d, b = %d, c = %d\n", t->a, t->b, t->c);
}
// Вычисление периметра треугольника
double TrianglePerimeter(Triangle *t) {
return (double)(t->a + t->b + t->c);
}
// Вычисление площади треугольника
double TriangleArea(Triangle *t) {
double p = (double)(t->a + t->b + t->c) / 2.0;
return sqrt(p*(p - t->a)*(p - t->b)*(p - t->c));
}
//==============================================================================
// Обработчики специализаций для треугольника
//==============================================================================
// Ввод треугольника как фигуры
void FigureIn<Figure.trian *f>(FILE* ifst) {
TriangleIn(&(f->@), ifst);
}
// Вывод треугольника как фигуры
void FigureOut<Figure.trian *f>(FILE* ofst) {
TriangleOut(&(f->@), ofst);
}
// Периметр треугольника как фигуры
double FigurePerimeter<Figure.trian *f>() {
return TrianglePerimeter(&(f->@));
}
// Площадь треугольника как фигуры
double FigureArea<Figure.trian *f>() {
return TriangleArea(&(f->@));
}
Примечание. Следует отметить, что на текущий момент абстрактные функции обобщений нельзя пока реализовать как описания прототипов. Поэтому их приходится оформлять в виде реализаций абстрактных или пустых функций. Но это будет исправляться в ходе дальнейших разработок...
Если же алгоритмы, реализуемые с основах специализации, достаточно простые, то их можно непосредственно реализовать в обработчиках специализаций. Заодно это ускорит обработку данных. Приведенный выше код для специализаций фигуры (прямоугольника и треугольника) тогда будет выглядеть следующим образом (эта версия программы расположена в подкаталоге "all/03-oop-ppc-direct/" архива).
//------------------------------------------------------------------------------
// rectangle.c - имитация методов прямоугольника
// Ввод прямоугольника как фигуры
void FigureIn<Figure.rect *f>(FILE* ifst) {
fscanf(ifst, "%d", &(f->@x));
fscanf(ifst, "%d", &(f->@y));
}
// Вывод прямоугольника как фигуры
void FigureOut<Figure.rect *f>(FILE* ofst) {
fprintf(ofst, "It is Rectangle: x = %d, y = %d\n", f->@x, f->@y);
}
// Периметр прямоугольника как фигуры
double FigurePerimeter<Figure.rect *f>() {
return (double)(2*(f->@x + f->@y));
}
// Площадь прямоугольника как фигуры
double FigureArea<Figure.rect *f>() {
return (double)(f->@x * f->@y);
}
//------------------------------------------------------------------------------
// triangle.c - имитация методов треугольника
// Ввод треугольника как фигуры
void FigureIn<Figure.trian *f>(FILE* ifst) {
fscanf(ifst, "%d", &(f->@a));
fscanf(ifst, "%d", &(f->@b));
fscanf(ifst, "%d", &(f->@c));
}
// Вывод треугольника как фигуры
void FigureOut<Figure.trian *f>(FILE* ofst) {
fprintf(ofst, "It is Triangle: a = %d, b = %d, c = %d\n",
f->@a, f->@b, f->@c);
}
// Периметр треугольника как фигуры
double FigurePerimeter<Figure.trian *f>() {
return (double)(f->@a + f->@b + f->@c);
}
// Площадь треугольника как фигуры
double FigureArea<Figure.trian *f>() {
double p = (double)(f->@a + f->@b + f->@c) / 2.0;
return sqrt(p*(p - f->@a)*(p - f->@b)*(p - f->@c));
}
Реализация функций контейнера также размещена с отдельной единице компиляции.
//------------------------------------------------------------------------------
// container.c
// Инициализация контейнера
void ContainerInit(Container *c) {
c->len = 0;
}
// Очистка контейнера от элементов (освобождение памяти)
void ContainerClear(Container *c) {
for(int i = 0; i < c->len; i++) {
free(c->cont[i]);
}
ContainerInit(c);
}
// Ввод содержимого контейнера
void ContainerIn(Container* c, FILE* ifst) {
while(!feof(ifst)) {
if((c->cont[c->len] = FigureCreate(ifst)) != 0) {
c->len++;
}
}
}
// Вывод содержимого контейнера
void ContainerOut(Container *c, FILE* ofst) {
fprintf(ofst, "Container contains %d elements.\n", c->len);
for(int i = 0; i < c->len; i++) {
fprintf(ofst, "%d: " , i);
FigureOut<c->cont[i]>(ofst);
}
}
// Вывод периметров фигур
void ContainerPerimeterOut(Container *c, FILE* ofst) {
fprintf(ofst, "Perimeters of figures:\n");
double sum = 0.0;
for(int i = 0; i < c->len; i++) {
double p = FigurePerimeter<c->cont[i]>();
fprintf(ofst, "%d: %lf\n" , i, p);
sum += p;
}
fprintf(ofst, "Perimeter sum = %lf\n", sum);
}
// Вывод площадей фигур
void ContainerAreaOut(Container *c, FILE* ofst) {
fprintf(ofst, "Areas of figures:\n");
double sum = 0.0;
for(int i = 0; i < c->len; i++) {
double a = FigureArea<c->cont[i]>();
fprintf(ofst, "%d: %lf\n" , i, a);
sum += a;
}
fprintf(ofst, "Area sum = %lf\n", sum);
}
Клиентский код аналогичен ОО версии, но с процедурным колоритом.
int main(int argc, char* argv[]) {
if(argc !=3) {
printf("incorrect command line!\nWaited: command infile outfile\n");
return 1;
}
FILE* ifst = fopen(argv[1], "r");
FILE* ofst = fopen(argv[2], "w");
Container c;
ContainerInit(&c);
ContainerIn(&c, ifst);
fclose(ifst);
fprintf(ofst, "Filled container.\n");
ContainerOut(&c, ofst);
ContainerPerimeterOut(&c, ofst);
ContainerAreaOut(&c, ofst);
ContainerClear(&c);
fprintf(ofst, "Empty container.\n");
ContainerOut(&c, ofst);
fclose(ofst);
return 0;
}
Имитация классов, использующих множественное наследование
В C++ отсутствуют интерфейсы. Вместо них используются абстрактные классы и множественное наследование, задавая тем самым наследование от нескольких интерфейсов. Отсутствие в процедурно-параметрической парадигме программирования (4П) аналогичных понятий ведет к другим решениям для реализации аналогичных свойств при использовании имитации.
ОО программа, использующая множественное наследование
Выделим реализацию ввода-вывода и вычислений в отдельные интерфейсы (программа расположена в подкаталоге "all/04-oop-c++interface/" архива). В результате будут сформированы три абстрактных класса, каждый из которых отвечает за свою функциональность:
класс фигуры Figure обеспечивает общий доступ к альтернативам, размещенным в контейнере, а его статический метод позволяет создавать конкретные фигуры;
класс ввода-вывода InOut используется для объявления интерфейсов соответствующих виртуальных методов;
класс вычислений Calc задает интерфейсы для методов вычисления периметра и площади фигур.
// figure.h - класс, обобщающий все фигуры.
class Figure {
public:
// иденитификация, порождение и ввод фигуры из потока
static Figure* Create(std::ifstream &ifst);
virtual void Empty() {}
virtual ~Figure() {};
};
// inout.h - класс, обобщающий ввод-вывод.
class InOut {
public:
virtual void In(std::ifstream &ifst) = 0; // ввод данных из файла
virtual void Out(std::ofstream &ofst) = 0; // вывод данных в файл
};
// calc.h - класс, обобщающий вычислительные методы
class Calc {
public:
virtual double Perimeter() = 0; // вычисление периметра
virtual double Area() = 0; // вычисление площади
};
Наличие пустого виртуального метода в фигуре обеспечивает создание таблицы виртуальных методов, через которую осуществляется динамическая проверка типов во время выполнения.
Производные классы конкретных геометрических фигур наследуют от всех трех интерфейсных классов и реализуют их методы.
// rectangle.h - прямоугольник
class Rectangle: public InOut, public Figure, public Calc {
int x, y; // ширина, высота
public:
// переопределяем интерфейс класса
virtual void In(std::ifstream &ifst); // ввод данных из файла
virtual void Out(std::ofstream &ofst); // вывод данных в файл
virtual double Perimeter(); // вычисление периметра
virtual double Area(); // вычисление площади
Rectangle(): x{0}, y{0} {} // создание без инициализации.
virtual ~Rectangle() {}
};
// rectangle.cpp
// Ввод параметров прямоугольника
void Rectangle::In(std::ifstream &ifst) {
ifst >> x >> y;
}
// Вывод параметров прямоугольника
void Rectangle::Out(std::ofstream &ofst) {
ofst << "It is Rectangle: x = " << x << ", y = " << y << "\n";
}
// Вычисление периметра прямоугольника
double Rectangle::Perimeter() {
return (double)(2*(x + y));
}
// Вычисление площади прямоугольника
double Rectangle::Area() {
return (double)(x * y);
}
//------------------------------------------------------------------------------
// triangle.h - треугольник
class Triangle: public Figure, public InOut, public Calc {
int a, b, c; // стороны
public:
// переопределяем интерфейс класса
virtual void In(std::ifstream &ifst); // ввод данных из файла
virtual void Out(std::ofstream &ofst); // вывод данных в файл
virtual double Perimeter(); // вычисление периметра
virtual double Area(); // вычисление площади
Triangle(): a{0}, b{0}, c{0} {} // создание без инициализации.
virtual ~Triangle() {}
};
// triangle.cpp
// Ввод параметров треугольника
void Triangle::In(std::ifstream &ifst) {
ifst >> a >> b >> c;
}
// Вывод параметров треугольника
void Triangle::Out(std::ofstream &ofst) {
ofst << "It is Triangle: a = "
<< a << ", b = " << b
<< ", c = " << c << "\n";
}
// Вычисление периметра треугольника
double Triangle::Perimeter() {
return (double)(a + b + c);
}
// Вычисление площади треугольника
double Triangle::Area() {
auto p = double(a + b + c) / 2.0;
return sqrt(p*(p-a)*(p-b)*(p-c));
}
Полиморфные методы ввода используются при вводе фигур из файла с данными после предварительной идентификации признака фигуры.
// figure.cpp - создание фигуры по ее признаку в файле и полиморфный ввод
Figure* Figure::Create(std::ifstream &ifst) {
Rectangle* pr;
Triangle* pt;
int k;
ifst >> k;
switch(k) {
case 1:
pr = new Rectangle;
pr->In(ifst);
return pr;
case 2:
pt = new Triangle;
pt->In(ifst);
return pt;
}
return nullptr;
}
Класс контейнера, по сравнению с предыдущей программой не изменяется. Однако изменения затрагивают реализацию методов контейнера. Так как эти методы отделены от описания обобщенной фигуры, получение нужного интерфейса и, следовательно, вызов нужного метода необходимо осуществлять с использованием динамического приведения типа во время выполнения через dynamic_cast.
// 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 Out(); // вывод фигур в файл
void PerimeterOut(std::ofstream &ofst); // вывод периметров в файл
void AreaOut(std::ofstream &ofst); // вывод площадей в файл
void Clear(); // очистка контейнера от фигур
Container(); // инициализация контейнера
~Container() {Clear();} // утилизация контейнера перед уничтожением
};
// container.cpp
// Инициализация контейнера
Container::Container(): len{0} { }
// Очистка контейнера от элементов (освобождение памяти)
void Container::Clear() {
for(int i = 0; i < len; i++) {
delete cont[i];
}
len = 0;
}
// Ввод содержимого контейнера
void Container::In(std::ifstream &ifst) {
while(!ifst.eof()) {
if((cont[len] = Figure::Create(ifst)) != 0) {
len++;
}
}
}
// Вывод содержимого контейнера
void Container::Out(std::ofstream &ofst) {
ofst << "Container contents " << len << " elements.\n";
for(int i = 0; i < len; i++) {
ofst << i << ": ";
// InOut* pio = dynamic_cast<InOut*>(cont[i]);
dynamic_cast<InOut*>(cont[i])->Out(ofst);
}
}
// Вывод периметров в файл
void Container::PerimeterOut(std::ofstream &ofst) {
ofst << "Perimeters of figures:\n";
auto sum = 0.0;
for(int i = 0; i < len; i++) {
auto p = dynamic_cast<Calc*>(cont[i])->Perimeter();
sum += p;
ofst << i << ": " << p << "\n";
}
ofst << "Perimeters sum = " << sum << "\n";
}
// Вывод площадей в файл
void Container::AreaOut(std::ofstream &ofst) {
ofst << "Areas of figures:\n";
auto sum = 0.0;
for(int i = 0; i < len; i++) {
auto a = dynamic_cast<Calc*>(cont[i])->Area();
sum += a;
ofst << i << ": " << a << "\n";
}
ofst << "Area sum = " << sum << "\n";
}
Использование в программе множественного наследования не отражается на клиенте, который по сравнению с предыдущим ОО решением остается неизменным.
Процедурно-параметрическая имитация в случае множественного наследования
Отсутствие множественного наследования в целом не мешает совместному использованию аналогов интерфейсов в процедурно-параметрическом программировании, которые можно имитировать через обобщения и полиморфные функции, обеспечивающие их обработку. Однако при этом необходимы дополнительные действия, которые в чем-то аналогичны динамическому приведению типов во время выполнения, используемому в ОО примере. Для имитации сформируем следующие обобщения (программа расположена в подкаталоге "all/05-oop-ppc-interface/" архива).
// figure.h - обобщение фигуры
typedef struct Figure {} <> Figure;
// идентификация, порождение и ввод фигуры из потока
Figure* FigureCreate(FILE* ifst);
// Функции - оболочки над интерфейсными функциями
// для приведения к нужным интерфейсам от фигуры
// Обобщающая функция для ввода
void FigureIn<Figure *f>(FILE* file) {} //= 0;
// Обобщающая функция для вывода
void FigureOut<Figure *f>(FILE* file) {} //= 0;
// Обобщающая функция для периметра
double FigurePerimeter<Figure *f>() {return 0.0;} //= 0;
// Обобщающая функция для площади
double FigureArea<Figure *f>() {return 0.0;} //= 0;
//------------------------------------------------------------------------------
// inout.h - обобщение ввода-вывода
typedef struct InOut {} <> InOut;
// inout.c
// Обобщающая функция для ввода
void In<InOut* f>(FILE* file) {} //= 0;
// Обобщающая функция для вывода
void Out<InOut* f>(FILE* file) {} //= 0;
//------------------------------------------------------------------------------
// calc.h - обобщение вычислений
typedef struct Calc {} <> Calc;
// calc.c
// Обобщающая функция для периметра
double Perimeter<Calc *c>() {return 0.0;} //= 0;
// Обобщающая функция для площади
double Area<Calc *c>() {return 0.0;} //= 0;
Каждая из конкретных фигур является основой специализации для псевдоинтерфейсов. При этом специализация для фигур непосредственно включает основу, а специализации ввода вывода и вычислений используют подключение основ через указатели.
// rectangle.h - прямоугольник как фигура
typedef struct Rectangle {
int x, y; // ширина, высота
} Rectangle;
Figure + < rect: Rectangle; >; // фигура - прямоугольник
// прямоугольник как участник ввода-вывода
InOut + < rect: Rectangle*; >; // указатель для подключения к основе фигуры
// прямоугольник как участник вычислений
Calc + < rect: Rectangle*; >; // указатель для подключения к основе фигуры
// triangle.h - треугольник
typedef struct Triangle {
int a, b, c; // стороны треугольника
} Triangle;
Figure + < trian: Triangle; >; // фигура - треугольник
// треугольник как участник ввода-вывода
InOut + < trian: Triangle*; >; // указатель для подключения к основе фигуры
// треугольник как участник вычислений
Calc + < trian: Triangle*; >; // указатель для подключения к основе фигуры
Обработчики специализаций для "интерфейсов" ввода-вывода и вычислений реализуют необходимую обработку для конкретных фигур, тем самым скрывая этот код от обобщенной фигуры и ее специализаций.
// rectangle.c
// Ввод прямоугольника
void In<InOut.rect *f>(FILE* ifst) {
fscanf(ifst, "%d", &(f->@->x));
fscanf(ifst, "%d", &(f->@->y));
}
// Вывод прямоугольника
void Out<InOut.rect *f>(FILE* ofst) {
fprintf(ofst, "It is Rectangle: x = %d, y = %d\n", f->@->x, f->@->y);
}
// Периметр прямоугольника
double Perimeter<Calc.rect *f>() {
return (double)(2*(f->@->x + f->@->y));
}
// Площадь прямоугольника
double Area<Calc.rect *f>() {
return (double)(f->@->x * f->@->y);
}
//------------------------------------------------------------------------------
// Ввод треугольника как фигуры
void In<InOut.trian *f>(FILE* ifst) {
fscanf(ifst, "%d", &(f->@->a));
fscanf(ifst, "%d", &(f->@->b));
fscanf(ifst, "%d", &(f->@->c));
}
// Вывод треугольника как фигуры
void Out<InOut.trian *f>(FILE* ofst) {
fprintf(ofst, "It is Triangle: a = %d, b = %d, c = %d\n",
f->@->a, f->@->b, f->@->c);
}
// Периметр треугольника как фигуры
double Perimeter<Calc.trian *f>() {
return (double)(f->@->a + f->@->b + f->@->c);
}
// Площадь треугольника как фигуры
double Area<Calc.trian *f>() {
double p = (double)(f->@->a + f->@->b + f->@->c) / 2.0;
return sqrt(p*(p - f->@->a)*(p - f->@->b)*(p - f->@->c));
}
Для доступа из фигуры к другими интерфейсам используются соответствующие полиморфные функции-обертки ввода-вывода и вычисления периметров и площадей. Они переопределяются для каждой конкретной фигуры, обеспечиваю доступ к соответствующим интерфейсам. Каждая оберточная функция обеспечивает подключение основы специализации конкретной фигуры к ее специализированному интерфейсу, используя при этом доступ к скрытому от нее обработчику соответствующей специализации.
//==============================================================================
// Оберточные обработчики, обеспечивающие преобразование "интерфейса" фигуры
// в "интерфейсы" ввода-вывода и вычислений
//==============================================================================
// rectangle.c
// Ввод прямоугольника как фигуры
void FigureIn<Figure.rect *f>(FILE* ifst) {
struct InOut.rect r;
r.@ = &(f->@);
In<(InOut*)&r>(ifst);
}
// Вывод прямоугольника как фигуры
void FigureOut<Figure.rect *f>(FILE* ofst) {
struct InOut.rect r;
r.@ = &(f->@);
Out<(InOut*)&r>(ofst);
}
// Периметр прямоугольника как фигуры
double FigurePerimeter<Figure.rect *f>() {
struct Calc.rect r;
r.@ = &(f->@);
return Perimeter<(Calc*)&r>();
}
// Площадь прямоугольника как фигуры
double FigureArea<Figure.rect *f>() {
struct Calc.rect r;
r.@ = &(f->@);
return Area<(Calc*)&r>();
}
//------------------------------------------------------------------------------
// triangle.c
// Ввод треугольника как фигуры
void FigureIn<Figure.trian *f>(FILE* ifst) {
struct InOut.trian t;
t.@ = &(f->@);
In<(InOut*)&t>(ifst);
}
// Вывод треугольника как фигуры
void FigureOut<Figure.trian *f>(FILE* ofst) {
struct InOut.trian t;
t.@ = &(f->@);
Out<(InOut*)&t>(ofst);
}
// Периметр треугольника как фигуры
double FigurePerimeter<Figure.trian *f>() {
struct Calc.trian t;
t.@ = &(f->@);
return Perimeter<(Calc*)&t>();
}
// Площадь треугольника как фигуры
double FigureArea<Figure.trian *f>() {
struct Calc.trian t;
t.@ = &(f->@);
return Area<(Calc*)&t>();
}
Это решение отличается от того, которое реализовано в случае ОО подхода. Однако оно позволяет не изменять код контейнера, его функций, а также клиента.
Имитация статической утиной типизации
Статическая утиная типизация (СУТ) имитируется по аналогии с интерфейсами Go. Основная идея заключается в формировании обобщенных структур, моделирующих интерфейсы по аналогии с таблицами виртуальных методов ОО языков. Подключение обрабатываемых данных к интерфейсам осуществляется через указатели.
Простая полиморфная Go программа
Как и в процедурном подходе исходные структуры в Go независимы и описывают базовые фигуры, используемые в качестве основ специализаций, подключаемых к интерфейсам. Однако вместо обычных функций реализуются функции, которые компиляторам могут быть привязаны к различным интерфейсам за счет выделения структуры в качестве связующего параметра. Также имена и параметры этих функци должны совпадать с соответствующими им функциями интерфейсов.
Для прямоугольника сформируем следующий пакет (детали опущены и имеются в исходных текстах, представленных в подкаталоге "all/06-sdt-go/" архива).
// rectangle.go - содержит описания структуры и функций прямоугольника
package rectangle
// прямоугольник
type Rectangle struct {
x int // ширина
y int // высота
}
// Ввод параметров прямоугольника из файла
func (r *Rectangle)In(inFile *os.File) error {
var err error
_, err = fmt.Fscan(inFile, &r.x)
_, err = fmt.Fscan(inFile, &r.y)
return err
}
// Формирование параметров для вывода прямоугольника в строке
func (r *Rectangle)String() string {
str := "It is Rectangle: x = " + strconv.Itoa(r.x) + ", y = " + strconv.Itoa(r.y)
return str
}
// Вычисление периметра прямоугольника
func (r *Rectangle)Perimeter() float64 {
return float64((r.x + r.y) * 2)
}
// Вычисление площади прямоугольника
func (r *Rectangle)Area() float64 {
return float64(r.x * r.y)
}
Аналогичным образом сформирован пакет для треугольников.
// triangle.go - содержит описание треугольника
package triangle
// треугольник
type Triangle struct {
// стороны
a int
b int
c int
}
// Ввод параметров треугольника из файла
func (t *Triangle)In(inFile *os.File) error {
var err error
_, err = fmt.Fscan(inFile, &t.a)
_, err = fmt.Fscan(inFile, &t.b)
_, err = fmt.Fscan(inFile, &t.c)
return err
}
// Формирование параметров для вывода треугольника в строке
func (t *Triangle)String() string {
str := "It is Triangle: a = " + strconv.Itoa(t.a) +
", b = " + strconv.Itoa(t.b) + ", c = " + strconv.Itoa(t.c)
return str
}
// Вычисление периметра треугольника
func (t *Triangle)Perimeter() float64 {
return float64(t.a + t.b + t.c)
}
// Вычисление площади треугольника
func (t *Triangle)Area() float64 {
var p = float64(t.a + t.b + t.c) / 2.0
return math.Sqrt(p * (p - float64(t.a)) * (p - float64(t.b)) * (p - float64(t.c)))
}
Пакет, определяющий интерфейс для всех фигур, также содержит функцию их ввода из файла.
// figure.go
package figure
// фигура как интерфейс
type Figure interface {
In(inFile *os.File) error
Perimeter() float64
Area() float64
}
// newfig.go
// Ввод и создание фигуры с подключением ее к интерфейсу
func NewFigure(key int) Figure {
var f Figure
switch key {
case 1: // Rectangle
r := new(rectangle.Rectangle)
f = r
return f
case 2: // Triangle
t := new(triangle.Triangle)
f = t
return f
default:
panic(fmt.Sprintf("Incorrect figure key = %d", key))
}
return nil
}
Функции контейнера обеспечивают манипуляции фигурами через сформированный интерфейс.
// container.go - содержит описание контейнера
package container
type Container struct {
length int
storage [100]figure.Figure
}
// Инициализация контейнера
func (c *Container )Init() {
c.length = 0
}
// Очистка контейнера от элементов (освобождение памяти)
func (c *Container )Clear() {
c.length = 0
}
// Ввод содержимого контейнера из указанного потока
func (c *Container )In(inFile *os.File) {
var err error
for err != io.EOF {
var key int
_, err = fmt.Fscan(inFile, &key)
if err != nil {
if err == io.EOF {
return
}
fmt.Println(err)
panic(fmt.Sprintf("Incorrect key =", key))
}
f := figure.NewFigure(key)
err = f.In(inFile)
c.storage[c.length] = f
c.length++
}
}
// Вывод содержимого контейнера в указанный поток
// С использованием интерфейсов, размещенных в пакете fmt
func (c *Container )Out(outFile *os.File) {
str := "Container contains " + strconv.Itoa(c.length) + " elements"
fmt.Fprintln(outFile, str)
for i := 0; i < c.length; i++ {
elem := strconv.Itoa(i+1) + ": "
fmt.Fprint(outFile, elem)
v := c.storage[i]
//v.Out(outFile)
fmt.Fprintln(outFile, v)
}
}
// Вывод периметров
func (c *Container )Perimeter(outFile *os.File) {
sum := 0.0
fmt.Fprintln(outFile, "List of Perimeters:")
for i := 0; i < c.length; i++ {
elem := strconv.Itoa(i+1) + ": "
fmt.Fprint(outFile, elem)
v := c.storage[i]
//fp = &v
p := v.Perimeter()
sum += p
fmt.Fprintln(outFile, p)
}
fmt.Fprint(outFile, "Sum of Perimeters = ")
fmt.Fprintln(outFile, sum)
}
// Вывод площадей
func (c *Container )Area(outFile *os.File) {
sum := 0.0
fmt.Fprintln(outFile, "List of Areas:")
for i := 0; i < c.length; i++ {
elem := strconv.Itoa(i+1) + ": "
fmt.Fprint(outFile, elem)
v := c.storage[i]
a := v.Area()
sum += a
fmt.Fprintln(outFile, a)
}
fmt.Fprint(outFile, "Sum of Areas = ")
fmt.Fprintln(outFile, sum)
}
Клиентский код реализует необходимую демонстрацию.
// main.go
package main
func main() {
var err error
// Выделение входного и выходного файлов из командной строки
inFilename, outFilename, err := filenamesFromCommandLine()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("inFilename = ", inFilename)
fmt.Println("outFilename = ", outFilename)
inFile, outFile := os.Stdin, os.Stdout
if inFilename != "" {
if inFile, err = os.Open(inFilename); err != nil {
log.Fatal(err)
}
defer inFile.Close()
}
if outFilename != "" {
if outFile, err = os.Create(outFilename); err != nil {
log.Fatal(err)
}
defer outFile.Close()
}
var c container.Container
c.Init()
c.In(inFile)
c.Out(outFile)
c.Perimeter(outFile)
c.Area(outFile)
c.Clear();
}
// Обработка данных из командной строки
func filenamesFromCommandLine() (inFilename, outFilename string, err error) {
if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
err = fmt.Errorf("usage: %s [<]infile.txt [>]outfile.txt",
filepath.Base(os.Args[0]))
return "", "", err
}
if len(os.Args) > 1 {
inFilename = os.Args[1]
if len(os.Args) > 2 {
outFilename = os.Args[2]
}
}
if inFilename != "" && inFilename == outFilename {
log.Fatal("won't overwrite the infile")
}
return inFilename, outFilename, nil
}
Процедурно-параметрическая имитация статической утиной типизации Go
Основное отличие статической утиной имитации от объектно-ориентированной заключается в том, что специализации подключаются к обобщению не напрямую, а через указатели. Это позволяет использовать одни и те же основы специализаций в различных обобщениях, что и поддерживается в Go. При этом само обобщение и его интерфейсная часть практически не изменяются, что позволяет говорить о возможности одновременной имитации обоих рассматриваемых подходов (программа расположена в подкаталоге "all/07-sdt-ppc/" архива).
// figure.h - структура, обобщающая фигуры
typedef struct Figure {} <> Figure;
// иденитификация, порождение и ввод фигуры из потока
Figure* FigureCreate(FILE* ifst);
// Удаление основы из фигуры
void FigureClear<Figure *f>();
// Прототип обобщеннай функции ввода фигуры
void FigureIn<Figure *f>(FILE* file);
// Прототип обобщеннай функции вывода фигуры
void FigureOut<Figure *f>(FILE* ofst);
// Прототип обобщенной функции вычисления периметра
double FigurePerimeter<Figure *f>();
// Прототип обобщенной функции вычисления площади
double FigureArea<Figure *f>();
// rectangle-.h - прямоугольник и его использование в имитации интерфейса
typedef struct Rectangle {
int x, y; // ширина, высота
} Rectangle;
Figure + < rect: Rectangle*; >; // фигура - прямоугольник
// triangle.h - треугольник
typedef struct Triangle {
int a, b, c; // стороны треугольника
} Triangle;
Figure + < trian: Triangle*; >; // фигура - треугольник
Следует отметить, что в отличие от Go, где подключения различных структур к интерфейсам автоматически поддерживаются компилятором, в PPC формирование обобщений осуществляется явным образом. Однако, в отличие от zig, строить для достижения тех же целей дополнительных моделей полиморфных отношений не нужно, так как, аналогично ООП, напрямую используется ПП полиморфизм.
Так как подключение основ специализаций (конкретных фигур) осуществляется через указатели, в реализации функций добавляется их создание в динамической памяти.
// figures-input.c - ввод параметров одной из фигур из файла
Figure* FigureCreate(FILE* ifst) {
Figure *sp;
struct Figure.rect *spr;
struct Figure.trian *spt;
int k = 0;
fscanf(ifst, "%d", &(k));
switch(k) {
case 1:
spr = create_spec(Figure.rect);
spr->@ = malloc(sizeof(Rectangle));
sp = (Figure*)spr;
break;
case 2:
spt = create_spec(Figure.trian);
spt->@ = malloc(sizeof(Triangle));
sp = (Figure*)spt;
break;
default:
return 0;
}
FigureIn<sp>(ifst);
return sp;
}
Фигура в данной ситуации, в отличие от ОО имитации, формируется из головной части и основы специализации, что добавляет в код дополнительные функции по созданию и удалению основ специализаций. Хотя в целом, по сравнению с ОО решением изменения в основном косметические.
//------------------------------------------------------------------------------
// rectangle.c
// Ввод параметров прямоугольника из файла
void RectangleIn(Rectangle *r, FILE* ifst) {
fscanf(ifst, "%d", &(r->x));
fscanf(ifst, "%d", &(r->y));
}
// Вывод прямоугольника
void RectangleOut(Rectangle *r, FILE* ofst) {
fprintf(ofst, "It is Rectangle: x = %d, y = %d\n", r->x, r->y);
}
// Вычисление периметра прямоугольника
double RectanglePerimeter(Rectangle *r) {
return (double)(2*(r->x + r->y));
}
// Вычисление площади прямоугольника
double RectangleArea(Rectangle *r) {
return (double)(r->x * r->y);
}
//==============================================================================
// Обработчики специализаций для прямоугольника
//------------------------------------------------------------------------------
// Удаление прямоугольника из фигуры
void FigureClear<Figure.rect *f>() {
free(f->@);
}
// Ввод прямоугольника как фигуры
void FigureIn<Figure.rect *f>(FILE* ifst) {
RectangleIn(f->@, ifst);
}
// Вывод прямоугольника как фигуры
void FigureOut<Figure.rect *f>(FILE* ofst) {
RectangleOut(f->@, ofst);
}
// Периметр прямоугольника как фигуры
double FigurePerimeter<Figure.rect *f>() {
return RectanglePerimeter(f->@);
// return (double)(2*(f->@x + f->@y));
}
// Площадь прямоугольника как фигуры
double FigureArea<Figure.rect *f>() {
return RectangleArea(f->@);
}
//------------------------------------------------------------------------------
// triangle.c
// Ввод параметров треугольника из файла
void TriangleIn(Triangle *t, FILE* ifst) {
fscanf(ifst, "%d", &(t->a));
fscanf(ifst, "%d", &(t->b));
fscanf(ifst, "%d", &(t->c));
}
// Вывод треугольника
void TriangleOut(Triangle *t, FILE *ofst) {
fprintf(ofst, "It is Triangle: a = %d, b = %d, c = %d\n", t->a, t->b, t->c);
}
// Вычисление периметра треугольника
double TrianglePerimeter(Triangle *t) {
return (double)(t->a + t->b + t->c);
}
// Вычисление площади треугольника
double TriangleArea(Triangle *t) {
double p = (double)(t->a + t->b + t->c) / 2.0;
return sqrt(p*(p - t->a)*(p - t->b)*(p - t->c));
}
//==============================================================================
// Обработчики специализаций для треугольника
//------------------------------------------------------------------------------
// Удаление треугольника из фигуры
void FigureClear<Figure.trian *f>() {
free(f->@);
}
// Ввод треугольника как фигуры
void FigureIn<Figure.trian *f>(FILE* ifst) {
TriangleIn(f->@, ifst);
}
// Вывод треугольника как фигуры
void FigureOut<Figure.trian *f>(FILE* ofst) {
TriangleOut(f->@, ofst);
}
// Периметр треугольника как фигуры
double FigurePerimeter<Figure.trian *f>() {
return TrianglePerimeter(f->@);
}
// Площадь треугольника как фигуры
double FigureArea<Figure.trian *f>() {
return TriangleArea(f->@);
}
Эти же косметические изменения по сравнении с ОО имитацией переносятся и в реализации функций контейнера.
// container.c
// Инициализация контейнера
void ContainerInit(Container *c) {
c->len = 0;
}
// Очистка контейнера от элементов (освобождение памяти)
void ContainerClear(Container *c) {
for(int i = 0; i < c->len; i++) {
FigureClear<c->cont[i]>();
free(c->cont[i]);
}
ContainerInit(c);
}
// Ввод содержимого контейнера
void ContainerIn(Container* c, FILE* ifst) {
while(!feof(ifst)) {
if((c->cont[c->len] = FigureCreate(ifst)) != 0) {
c->len++;
}
}
}
// Вывод содержимого контейнера
void ContainerOut(Container *c, FILE* ofst) {
fprintf(ofst, "Container contains %d elements.\n", c->len);
for(int i = 0; i < c->len; i++) {
fprintf(ofst, "%d: " , i);
FigureOut<c->cont[i]>(ofst);
}
}
// Вывод периметров фигур
void ContainerPerimeterOut(Container *c, FILE* ofst) {
fprintf(ofst, "Perimeters of figures:\n");
double sum = 0.0;
for(int i = 0; i < c->len; i++) {
double p = FigurePerimeter<c->cont[i]>();
fprintf(ofst, "%d: %lf\n" , i, p);
sum += p;
}
fprintf(ofst, "Perimeter sum = %lf\n", sum);
}
// Вывод площадей фигур
void ContainerAreaOut(Container *c, FILE* ofst) {
fprintf(ofst, "Areas of figures:\n");
double sum = 0.0;
for(int i = 0; i < c->len; i++) {
double a = FigureArea<c->cont[i]>();
fprintf(ofst, "%d: %lf\n" , i, a);
sum += a;
}
fprintf(ofst, "Area sum = %lf\n", sum);
}
Так как подключение основ специализаций осуществляется только через указатели, базовая часть специализированных фигур будет иметь одинаковый размер. Поэтому вместо массива указателей на фигуры можно использовать непосредственное хранение этой базовой части. Соответствующий пример размещен в подкаталоге "all/08-sdt-ppc-in-container/" пралагаемого архива.
Клиентский код остается неизменным.
Резюме
Можно отметить, что в рассмотренных выше простых ситуациях для ОО подхода наблюдается практически однозначное отображение базовых классов в обобщения, производных классов в специализации, методов классов в функции, виртуальных методов в обобщающие функции и обработчики специализаций. ОО интерфейсы и интерфейсы Go отображаются в структуры с указателями на основы специализаций. В целом имитация других видов динамического полиморфизма с применением ПП подхода смотрится гораздо проще чем формирование аналогичных моделей с применением обычного процедурного программирования.
Возможно для более сложных и заумных конфигураций классов и интерфейсов подобный трюк не проходит. Хотя процедурно-параметрические реализации большинства паттернов проектирования демонстрируют, что и здесь нет проблем получить аналогичные решения, которые при этом могут более гибко расширяться. Но в подобных ситуациях можно поступить и проще: использовать процедурно-параметрический подход без ориентации на объектную ориентированность и статическую утиную типизацию, формируя проект в соответствии с принципами процедурно-параметрического проектирования. Но это уже другая история.
Список используемых источников
Буч Г. Объектно-ориентированное проектирование с примерами применения. - M.: Конкорд, 1992. 519 с.
Денни Калев. Интервью с Бьерном Страуструпом. Будущее за мультипарадигменным программированием - 2001.
Сайт языка программирования Zig (на русском языке).
Изучаем язык программирования Zig.
Pedro Duarte Faria. Introduction to Zig: a project-based book.
|