#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>
/** ООП через инкапсуляцию **/
/* Проверка на выделение памяти опущена умышленно */
/* Обратите внимание, что вы можете написать функции печати и ИНАЧЕ,
* Имея функцию печати для *каждого* объекта не перетирая старую.
* Это позволит перед выводом новых полей вывести старые, вызвав функцию
* глубиной "ниже"
* Например:
* ~~~
* struct my_figure {
* struct my_object object;
* object_printer_t printer; // = _printer_figure
* }
* void _printer_figure(void * object) {
* struct my_figure * figure = object;
* // Вызываем принтер объекта, в котором печатаем имя и, например ptr
* figure->object.printer(figure->object);
* // Непосредственно новые данные фигуры
* printf(
* "Данные фигуры: %" PRIu32 ":%" PRIu32 "\n",
* figure->coords.x,
* figure->coords.y
* );
* }
* ~~~
* В объекте следующего уровня не нужно вызывать `figure->object.printer`
* Достаточно вызвать принтер непосредственного родителя:
* ~~~
* struct my_square * square = object;
* square->figure.printer(square->figure);
* <...>
* ~~~ */
/* Также можно применить union, для того, чтобы при доступе к полям структуры
* не перечислять всех родителей чтобы добраться до нужного поля:
* ~~~
* union my_square {
* struct my_object object;
* struct my_figure figure;
* struct {
* uint32_t width;
* } square;
* }
*
* my_square->square.width;
* my_square->figure.points;
* my_square->object.name;
* ~~~
* Это потребует перечислить все подлежащие объекты в объявлении (если, конечно,
* вы не хотите скрыть часть из них) */
/* Также вместо приведения типа в каждом месте можно использовать функцию,
* которая скроет от пользователя каст, что позволит иметь более чистый код
* плюс позволит достаточно быстро изменить структуру наследования:
* ~~~
* struct my_figure * _figure_get(void * ptr) {
* return (struct my_figure *)ptr;
* }
*
* struct my_figure * figure = _figure_get(square);
* ~~~
* Вы можете добавить защиту от дурака при помощи typeof самостоятельно,
* позволяя кастить только дозволенные объекты при условии, что тип объекта
* известен compile-time */
/*! \brief Функция печати данных объекта. */
typedef void (* object_printer_t)(void * object);
/* ## Первый уровень */
/*! \brief Объект-родитель */
struct my_object {
object_printer_t printer; /**< Функция печати данных объекта */
const char * name; /**< Имя объекта */
};
/*! \brief Функция печати объекта. Обычно замещается другой
* \param[in] object Объект `struct my_object *` */
static void _printer_object(void * object) {
struct my_object * my_object = object;
printf("Object: %s\n", my_object->name);
}
/*! \brief Конструктор объекта
* \param[in] name Имя объекта
* \return Объект */
static struct my_object * _create_object(const char * name) {
struct my_object * object = malloc(sizeof(struct my_object));
object->printer = _printer_object;
object->name = name;
return object;
}
/*! \brief Деструктор объекта
* \param[in] object Объект */
static void _destroy_object(struct my_object * object) {
free(object);
}
/* ## Второй уровень */
/*! \brief Пример первого уровня - Фигура */
struct my_figure {
struct my_object object; /**< Родительский объект */
uint32_t points; /**< Количество углов */
struct { /**< Координаты LT-угла */
uint32_t x; /**< Горизонтальная */
uint32_t y; /**< Вертикальная */
} coords;
};
/*! \brief Функция печати фигуры.
* \param[in] object Объект `struct my_figure *` */
static void _printer_figure(void * object) {
struct my_object * my_object = object;
struct my_figure * my_figure = object;
printf(
"Figure \"%s\": %" PRIu32 ":%" PRIu32 "\n",
my_object->name,
my_figure->coords.x,
my_figure->coords.y
);
}
/*! \brief Конструктор фигуры
* \param[in] name Имя фигуры
* \param[in] points Количество углов фигуры
* \param[in] x X-координата фигуры
* \param[in] y Y-координата фигуры
* \return Фигура */
static struct my_figure * _create_figure(
const char * name,
uint32_t points,
uint32_t x,
uint32_t y
) {
// Создаем объект на 1 уровень ниже
struct my_object * object = _create_object(name);
object->printer = _printer_figure;
// Создаем свойства текущего уровня
struct my_figure * figure = realloc(object, sizeof(struct my_figure));
figure->points = points;
figure->coords.x = x;
figure->coords.y = y;
return figure;
}
/*! \brief Деструктор фигура
* \param[in] object Фигура */
static void _destroy_figure(struct my_figure * figure) {
// Здесь мы должны вызвать деструкторы полей НАШЕГО объекта.
// У нас их нет. Иначе мы могли бы вызвать например free(figure->string);
// Вызываем деструктор родителя
_destroy_object((struct my_object *)figure);
}
/* ## Третий уровень */
/*! \brief Пример третьего уровня - Квадрат */
struct my_square {
struct my_figure figure; /**< Родительский объект */
uint32_t width; /**< Размер грани куба */
};
/*! \brief Функция печати фигуры.
* \param[in] object Объект `struct my_figure *` */
static void _printer_square(void * object) {
struct my_object * my_object = object;
struct my_figure * my_figure = object;
struct my_square * my_square = object;
printf(
"Square \"%s\": %" PRIu32 ":%" PRIu32 ", with a side of %" PRIu32 "\n",
my_object->name,
my_figure->coords.x,
my_figure->coords.y,
my_square->width
);
}
/*! \brief Конструктор квадрата
* \param[in] name Имя квадрата
* \param[in] width Ширина квадрата
* \param[in] x X-координата квадрата
* \param[in] y Y-координата квадрата
* \return Квадрат */
static struct my_square * _create_square(
const char * name,
uint32_t width,
uint32_t x,
uint32_t y
) {
static const uint32_t square_points = 4;
// Создаем объект на 1 уровень ниже
struct my_figure * figure = _create_figure(name, square_points, x, y);
// Нам всё еще нужен объект для переписываения принтера.
// См. одно из примечаний для того чтобы сделать иначе.
struct my_object * object = (struct my_object *)figure;
object->printer = _printer_square;
// Создаем свойства текущего уровня
struct my_square * square = realloc(figure, sizeof(struct my_square));
square->width = width;
return square;
}
/*! \brief Деструктор квадрата
* \param[in] object Объект */
static void _destroy_square(struct my_square * square) {
// Здесь мы должны вызвать деструкторы полей НАШЕГО объекта.
// У нас их нет. Иначе мы могли бы вызвать например free(square->alloced);
// Вызываем деструктор родителя
// См. одно из примечаний для того, чтобы не использовать каст
_destroy_figure((struct my_figure *)square);
}
int main()
{
struct my_object * object = _create_object("Just an object");
struct my_figure * figure = _create_figure("Figure", 5, 21, 34);
struct my_square * square = _create_square("Square", 50, 1, 2);
// Вы можете не использовать всю цепочку, если будете использовать union
// как в одном из примечаний.
object->printer(object);
figure->object.printer(figure);
square->figure.object.printer(square);
// Мы также можем использовать принтер в функции, которая принимает
// более неглубокую версию объекта:
struct my_object * casted_object = (struct my_object *)square;
casted_object->printer(casted_object);
// Мы всё равно получим вывод исходного объекта
// Ну и также мы можем использовать принтер более низкого уроня на более
// высокий объект:
figure->object.printer(square);
_destroy_square(square);
_destroy_figure(figure);
_destroy_object(object);
return 0;
}