|
© 2025
Александр Легалов
Содержание
Шаг второй. Добавление альтернативы
Добавление новых альтернатив, обладающих аналогичной функциональностью с уже имеющимися, является достаточно частым вариантом расширения программы. Использование каждого из рассматриваемых подходов проявляется по разному. Будем расширять код, написанный на первом шаге, путем добавления в него круга Circle, для которого задан радиус.
Круг и процедурный подход
Круг в процедурную программу добавляется как еще одна основа специализации. Поэтому данная структура и ее функции, являясь новыми артефактами, никоим образом не зависят от других программных объектов.
// circle.h - круг
typedef struct Circle {
int r; // радиус круга
} Circle;
// circle-in.c - ввод круга
void CircleIn(Circle *c, FILE* ifst) {
fscanf(ifst, "%d", &(c->r));
}
// circle-out.c - вывод круга
void CircleOut(Circle *c, FILE *ofst) {
fprintf(ofst, "It is Circle: r = %d\n", c->r);
}
Однако для обобщенной фигуры это добавление является болезненным. Приходится изменять код, описывающий признаки специализации (перечислимый тип), структуру обобщенной фигуры, функции ввода-вывода обобщенной фигуры. Именно проблемы с прямым расширением альтернатив являются болевой точкой процедурного подхода по сравнению с объектно-ориентированным.
// figure.h - структура, обобщающая фигуры
// значения ключей для каждой из фигур
typedef enum key {RECTANGLE, TRIANGLE, CIRCLE} key;
// структура, обобщающая все имеющиеся фигуры
typedef struct Figure {
key k; // ключ
// используемые альтернативы
union { // используем простейшую реализацию
Rectangle r;
Triangle t;
Circle c;
};
} Figure;
// 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);
return pf;
case 2:
pf = malloc(sizeof(Figure));
pf->k = TRIANGLE;
TriangleIn(&(pf->t), ifst);
return pf;
case 3: // пришлось добавить ввод круга
pf = malloc(sizeof(Figure));
pf->k = CIRCLE;
CircleIn(&(pf->c), ifst);
return pf;
default:
return 0;
}
}
// figure-out.c - вывод обобщенной фигуры
void FigureOut(Figure *s, FILE* ofst) {
switch(s->k) {
case RECTANGLE:
RectangleOut(&(s->r), ofst);
break;
case TRIANGLE:
TriangleOut(&(s->t), ofst);
break;
case CIRCLE: // пришлось добавить вывод круга
CircleOut(&(s->c), ofst);
break;
default:
fprintf(ofst, "Incorrect figure!\n");
}
}
Все остальные данные и функции не изменяются. Поэтому они повторно используются за счет непосредственного подключения файлов из проекта, реализованного в результате первого шага.
Схема, описывающая ситуацию после добавления в обобщение новой альтернативы показывает, что произошли изменения фигуры и ее функции, что связано с появлением новых зависимостей, которые должны предшествовать созданию обобщению. Восходящая разработка в данной ситуации ведет к модификации за счет незапланированных заранее изменений на более низком уровне.
Круг и объектно-ориентированный подход
Круг, как и предшествующие фигуры, наследует от базового класса. Поэтому добавление новой альтернативы и написание ее методов к модификации ранее написанного кода не приводят.
// circle.h - круг
class Circle: public Figure {
int r; // радиус
public:
// переопределяем интерфейс класса
void InData(std::ifstream &ifst); // ввод данных из потока
void Out(std::ofstream &ofst); // вывод данных в стандартный поток
Circle(): r{0} {} // создание без инициализации.
};
// circle-in.cpp - ввод круга
void Circle::InData(std::ifstream &ifst) {
ifst >> r;
}
// circle-out.cpp - вывод круга
void Circle::Out(std::ofstream &ofst) {
ofst << "It is Circle: r = " << r << "\n";
}
Также не вызывает проблем формирование фабрики кругов. Она идентична ранее сформированным фабрикам прямоугольников и треугольников.
// circle-factory.h - класс фабрики, порождающей круги
class CircleFactory: public FigureFactory {
int key; // Ключ фабрики, совпадающий с ключом в файле
public:
// иденитификация, порождение и ввод фигуры из потока
virtual Figure* Test(int k);
// Конструктор, регистрирующий ключ = 1 для кругов
CircleFactory();
};
// circle-factory.cpp
// Конструктор, регистрирующий ключ = 3 для кругов
CircleFactory::CircleFactory(): key{3} {
factory[counter++] = this;
}
// Реализация метода проверки и порождения круга
Figure* CircleFactory::Test(int k) {
if(k == key) {
return new Circle;
}
return nullptr;
}
// Регистратор фабрики кругов
namespace {
CircleFactory cf;
}
При данном расширении модификации ранее разработанных программных объектов не происходит. Все остальные заголовочные файлы и единицы компиляции повторно используются из первого шага путем включения в этот проект.
Отсутствие изменений в структуре обобщений демонстрируется на схеме. Добавление новой фигуры не влияет на базовый класс. Используемая в ОО подходе нисходящая разработка в данном случае позволяет получить более гибкое решение.
Круг и процедурно-параметрический подход
Расширение в 4П осуществляется использованием круга как основы специализации. Круг и функции его ввода-вывода полностью совпадают с теми, что были реализованы в процедурной программе. Поэтому, чтобы, все-таки, не смешивать разные парадигмы, я скопировал процедурные файлы в ПП проект. Так как обобщение для фигур уже было реализовано, то остается сформировать специализацию круга как фигуры и прописать для нее функции ввода-вывода.
// figure-circle.h - фигура-круг
Figure + < circ: Circle; >;
// figure-circle-in - ввод прямоугольника как фигуры
void FigureIn<Figure.circ *f>(FILE* ifst) {
CircleIn(&(f->@), ifst);
}
// figure-circle-out - вывод круга как фигуры
void FigureOut<Figure.circ *f>(FILE* ofst) {
Circle* c = &(f->@);
CircleOut(c, ofst);
}
Аналогичным образом расширяются перечислимый тип Input и добавляемая для идентификация круга функция (признак в файле равен 3), что обеспечивает безболезненное расширение ввода фигур, включая круг, из файла.
// figure-circle.h - признак для ввода круга из файла
Input + <circ: void;>;
// figure-circle-out - cоздание abuehs-круга при вводе из файла
Figure* FigureCreateUseInputTag<Input.circ *pFig>(int k) {
if(k == 3) {
return create_spec(Figure.circ);
} else {
return NULL;
}
}
Весь остальной код повторно используется из программы, разработанной на первом шаге.
Как и в случае ОО подхода, использование 4П позволяет добавлять новые альтернативы без изменения ранее написанного кода, при этом сохраняется специфика процедурного программирования, а также возможность использования для расширений программировани как сверху вниз, так и снизу вверх. Особенности добавления новых специализаций отражены на схеме.
Выводы
Прямое добавление новых альтернатив в процедурную программу ведет к постоянному изменению уже написанного кода. При этом явная проверка альтернативных типов во время выполнения не всегда является надежной. ОО полиморфизм в этой ситуации обеспечивает разделение альтернатив и их безболезненное расширение без изменения ранее написанного кода. Во многом это и определило популярность ООП. ПП программирование обеспечивает поддержку тех же возможностей по безболезненному добавлению альтернатив, что и ООП. При необходимости, используя 4П, можно даже писать программу в ОО стиле, когда внешние полиморфные функции будут напоминать процедуры, связанные с типом, языков программирования Оберон-2 и Ада-95.
Содержание
|