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

Процедурно-параметрическое программирование (ППП) — это парадигма, в которой полиморфизм достигается не через наследование и виртуальные методы, а через передачу функций в качестве параметров. В статье на Habr автор сравнил реализацию одной и той же задачи в ООП и ППП, получив впечатляющие различия в производительности: ППП-версия оказалась до 6 раз быстрее. Разберём, что это за подход, как он работает и какие выгоды может дать.
Свободные функции вместо методов: как работает процедурно-параметрическое программирование
В классическом ООП полиморфизм реализуется через иерархию классов и виртуальные методы. При вызове метода у объекта программа динамически определяет, какую реализацию вызвать, через таблицу виртуальных функций (vtable). Это накладывает накладные расходы: каждый вызов требует косвенного обращения и часто препятствует встраиванию (inlining) функций компилятором.
Процедурно-параметрическое программирование предлагает иной подход: функции (процедуры) остаются свободными, то есть не привязанными к классу, а полиморфизм обеспечивается передачей нужной функции в качестве аргумента. Например, вместо того чтобы иметь метод draw() у каждого класса фигуры, можно иметь функцию draw(circle, renderer), где renderer — функция, реализующая отрисовку. Таким образом, поведение параметризуется извне.
Автор статьи на Habr реализовал одну и ту же задачу — вычисление площади набора фигур — в ООП и ППП. В ООП использовались абстрактный базовый класс Shape с виртуальным методом area(), а в ППП — свободная функция area(shape, formula), где formula — указатель на функцию, вычисляющую площадь для конкретного типа фигуры.
Предыстория и контекст: почему об этом заговорили сейчас
Интерес к альтернативам ООП растёт на фоне развития языков с поддержкой функционального программирования. Rust, Go, а также современные C++ и C всё активнее используют концепции, близкие к ППП. В Rust, например, трейты (traits) позволяют реализовать полиморфизм без наследования, а передача замыканий (closures) — это по сути процедурно-параметрический подход.
ООП остаётся доминирующей парадигмой, но его недостатки — сложность иерархий, накладные расходы на виртуальные вызовы, проблемы с композицией — заставляют искать альтернативы. ППП предлагает более лёгкий и гибкий способ достижения полиморфизма, особенно в системах, где важна производительность.
В чём разница в производительности между ООП и процедурно-параметрическим подходом?
Автор статьи провёл численное сравнение. В ООП-реализации вызов виртуального метода area() для каждой фигуры требовал обращения к vtable и не мог быть встроен компилятором. В ППП-реализации функция area принимала указатель на конкретную формулу (например, circlearea или rectanglearea), и компилятор мог легко встроить вызов, так как он был прямым, а не косвенным.
Результаты показали, что по метрике «время выполнения» ППП-версия обогнала ООП в 6 раз на наборе из 10 000 фигур. Это объясняется тем, что встраивание устранило накладные расходы на вызов, а также позволило применить дальнейшие оптимизации, такие как свёртка констант.
Технические детали: как реализовать полиморфизм без классов
В процедурно-параметрическом подходе тип фигуры можно представить как структуру данных (например, struct Circle { radius: f64 }), а функцию для вычисления площади — как отдельную сущность. Полиморфизм достигается за счёт того, что разные структуры имеют разные функции-формулы, которые передаются в общую процедуру.
В коде это выглядит так:
c // OOP-стиль Shape shapes[2]; shapes[0] = new Circle(1.0); shapes[1] = new Rectangle(2.0, 3.0); double total = 0; for (int i = 0; i area();
// ППП-стиль Circle c = {1.0}; Rectangle r = {2.0, 3.0}; double total = 0; total += area(c, circlearea); total += area(r, rectarea);
Функция area принимает любой тип и указатель на функцию, которая знает, как вычислить площадь для этого типа. Компилятор может встроить circlearea и rectarea непосредственно в цикл, устраняя косвенность.
Кого затронет и как: разработчики, архитекторы, инженеры
Для разработчиков, работающих с высоконагруженными системами, игровыми движками, графикой или встраиваемыми системами, ППП может стать способом повысить производительность без усложнения кода. Особенно это актуально для языков, где виртуальные вызовы дороги (C++, Rust, C).
Архитекторам ПО стоит учитывать, что ППП упрощает композицию: вместо построения глубоких иерархий классов можно комбинировать данные и функции более свободно. Это снижает связанность и повышает тестируемость.
В российском контексте подход может заинтересовать команды, работающие над оптимизацией legacy-кода на C++: замена виртуальных методов на параметризованные функции может дать заметный прирост скорости без полного переписывания.
Что будет дальше: прогноз и развитие
Скорее всего, процедурно-параметрическое программирование не вытеснит ООП, но займёт свою нишу как эффективная техника для критичных к производительности участков кода. Уже сейчас многие фреймворки (например, в Rust) используют подобный подход. Автор статьи планирует продолжить исследования и опубликовать бенчмарки на более сложных примерах.
Вероятно, в ближайшие годы мы увидим больше публикаций и библиотек, реализующих ППП-идиомы, особенно в сообществах Rust и C++. Разработчикам стоит присмотреться к этому подходу уже сейчас.
Итог
Процедурно-параметрическое программирование — это не новая парадигма, но недооценённая. Сравнение с ООП показало, что отказ от виртуальных методов в пользу передачи функций может дать до 6-кратного ускорения. Это не панацея, но полезный инструмент для тех, кто ищет производительность без потери гибкости. Следите за развитием темы — возможно, ППП станет следующим трендом в системном программировании.