Во многих популярных современных объектно‑ориентированных языках (C#, Kotlin, Swift, Dart) есть такой механизм как extensions methods. Он позволяет добавить к классу необходимые методы и свойства извне, не меняя сам класс. Это очень удобный и полезный механизм, когда, например, мы хотим добавить вспомогательные методы в какой‑нибудь библиотечный класс, которым мы не владеем и не можем его изменить напрямую. Например, добавить к любому стороннему классу свой метод toString().
На примере Kotlin это выглядит очень просто и понятно что происходит в принципе любому, даже не знакомому с Kotlin:
class Person(val name: String)
fun Person.greet() = "Hello $name!"
fun main() {
val person = Person("John")
println(person.greet())
}Ещё одно преимущество расширений - это возможность реализовывать цепочки вызовов в функциональном стиле добавляя необходимые методы не только в свои классы, но и расширяя сторонние. Реальные примеры из жизни это LINQ в C#, Sequences в Kotlin, Streams в Java. Ещё один пример:
listOf(1, 2, 3).map { it.toString() }.joinToString(“,”)Если это писать в виде обычного вызова функций, то будет уже не так красиво, согласитесь:
joinToString(map(listOf(1,2,3) {it.toString}), “,”)Мы наблюдаем большое количество вложенности, обилие открывающих и закрывающих скобочек, код читается сложнее, можно запутаться какие параметры в какую функцию передаются.
Во многих динамических языках тоже думают что у них есть методы расширения, но это не совсем правда. Как правило в JavaScript, Ruby и прочих динамических языках мы можем динамически добавить метод в сам класс. Внешне это похоже на методы расширения. Но этот способ довольно опасен. Во первых все остальные пользователи этого класса увидят этот метод, во вторых можно сломать работу класса, если случайно перебить его существующий метод. К примеру, Вася в своей библиотеке добавляет в какой-нибудь системный класс метод toString(), вы, подключаю библиотеку Васи не подозреваете об этом и добавляете свой метод toString() в этот же класс, тем самым ломаете логику библиотеки Васи. Поэтому в динамических языках этот приём расширения классов не распространён и наказуем.
К сожалению, несмотря на ускорившиеся в последние годы темпы развития C++, и добавление кучи полезных фичей, таких как корутины, концепты, расширение стандартной библиотеки, ranges, threads.. механизм расширения почему-то игнорируют, хотя на мой взгляд это довольно простая фича, которая не сильно ломает существующий синтаксис. Даже Страуструп предлагал в 2014 году их добавить, прошло 10 лет, но увы пока мы этого не видим в языке и неизвестно когда увидим.
Отчасти данный подход реализован в библиотеке std::ranges, там есть так называемый pipe синтаксис:
auto const ints = {0, 1, 2, 3, 4, 5};
auto even = [](int i) { return 0 == i % 2; };
auto square = [](int i) { return i * i; };
// the "pipe" syntax of composing the views:
for (int i : ints | std::views::filter(even) | std::views::transform(square))
std::cout << i << ' ';Он расширяем, вы можете написать свою трансформацию. Я не разбирался с этим, но думаю там не сложно
