Статьи

Реактор проекта — деструктурирование кортежа

Кортежи — это простые структуры данных, которые содержат фиксированный набор элементов, каждый из которых имеет свой тип данных.

Project Reactor предоставляет структуру данных Tuple, которая может содержать около 8 различных типов.

Кортежи очень полезны, однако одна из проблем при использовании кортежей заключается в том, что трудно разобрать, что они держат, не разрушая их в каждом месте, где они используются.

проблема

Рассмотрим простой оператор, который объединяет строку и целое число следующим образом:

1
Mono<Tuple2<String,  Integer>> tup = Mono.zip(Mono.just("a"), Mono.just(2))

Результатом является кортеж, содержащий 2 элемента.

Теперь проблема, которая у меня есть, заключается в следующем. Я предпочитаю, чтобы кортеж разыменовывался в его составные элементы, прежде чем что-либо делать с ним.

Скажем, с предыдущим кортежем я хочу сгенерировать столько строк (первый элемент кортежа), сколько count (второй элемент кортежа) выражается следующим образом:

1
2
3
4
Mono.zip(Mono.just("a"), Mono.just(2))
    .flatMapMany(tup -> {
        return Flux.range(1, tup.getT2()).map(i -> tup.getT1() + i);
    })

Проблема здесь заключается в том, что при обращении к «tup.getT1 ()» и «tup.getT2 ()» не совсем понятно, что держит кортеж. Хотя более многословно я бы предпочел сделать что-то вроде этого:

1
2
3
4
5
6
7
Mono.zip(Mono.just("a"), Mono.just(2))
    .flatMapMany(tup -> {
        String s = tup.getT1();
        int count = tup.getT2();
 
        return Flux.range(1, count).map(i -> s + i);
    });

Решение

Подход деструктуризации в явные переменные со значимым именем работает, но он немного многословен. Гораздо лучший подход обеспечивается с помощью набора служебных функций, которые поставляются с проектным реактором.
TupleUtils

Я чувствую, что это лучше всего объяснить, используя пример, с TupleUtils предыдущая деструктуризация выглядит следующим образом:

1
2
3
Mono.zip(Mono.just("a"), Mono.just(2))
    .flatMapMany(TupleUtils.function((s, count) ->
            Flux.range(1, count).map(i -> s + i)))

Это выглядит гораздо более кратким, чем явная деструктуризация. Хотя есть немного сообразительности, к которой нужно привыкнуть:

Подпись flatMapMany является —

1
public final <R> Flux<R> flatMapMany(Function<? super T,? extends Publisher<? extends R>> mapper)

TupleUtils предоставляет другое косвенное обращение, которое возвращает требуемую выше функцию через другую функцию:

1
2
3
public static <T1, T2, R> Function<Tuple2<T1, T2>, R> function(BiFunction<T1, T2, R> function) {
    return tuple -> function.apply(tuple.getT1(), tuple.getT2());
}

Если вы используете Kotlin, возможен более простой подход. Это основано на концепции «уничтожающих деклараций» . Проектный реактор предоставляет набор вспомогательных утилит Kotlin, использующих дополнительную зависимость gradle:

1
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")

С этой зависимостью эквивалентный код Kotlin выглядит так:

1
2
3
4
Mono.zip(Mono.just("a"), Mono.just(2))
    .flatMapMany { (s: String, count: Int) ->
        Flux.range(1, count).map { i: Int -> s + i }
    }

Посмотрите, как кортеж был деструктурирован непосредственно в переменные. Это выглядит так изолированно:

1
val (s: String, count: Int) = tup

Вывод

Важно деструктурировать кортеж в более значимые переменные, чтобы улучшить читабельность кода, а полезные функции, предоставляемые TupleUtils, а также расширениями Kotlin, помогают сохранить код кратким, но читабельным.

Примеры Java здесь и Kotlin образцы здесь

Смотрите оригинальную статью здесь: Проектный реактор — деструктурирование кортежа

Мнения, высказанные участниками Java Code Geeks, являются их собственными.