Инкапсуляция встроенного метода reified в котлине

Я написал класс util для использования с Flow<> в котлине. Цель класса — упростить обработку ошибок и дать возможность мгновенно завершить поток, вызвав метод breakFlow(). По сравнению с return@flow, этот метод можно вызвать вне контекста потока (например, внутри небольшого частного метода, который затем вызывается в построителе потока).

Моя проблема в том, что я использую встроенную функцию withCatchingErrors() с параметром reified type. Я не хочу раскрывать эту функцию за пределами класса FlowUtils. Вместо этого следует раскрывать методы withBreakableFlow().

Я не могу избавиться от встроенной функции (или не знаю, как это сделать), потому что мне нужен параметр reified type, чтобы проверить тип поля data внутри исключения AppErrorAsThrowable. Без ключевого слова <reified T> я получил ошибку: Cannot check for instance of erased type: T?

Есть код:

class FlowUtils {
    class AppErrorAsThrowable private constructor(val error: AppError, val data: Any?) : Throwable() {
        companion object {
            fun create(error: AppError, data: Any?) = AppErrorAsThrowable(error, data)
        }
    }

    companion object {

        inline fun <reified T, R> FlowCollector<R>.withBreakableFlow(
            onBreakError: (breakError: Resource.Error<T>) -> Unit,
            flowImpl: () -> Unit
        ) {
            withCatchingErrors<T>(onBreakError, flowImpl)
        }

        fun <R> FlowCollector<R>.breakFlow(breakError: AppError, data: Any? = null): Nothing {
            throwError(breakError, data)
        }

        fun breakFlow(breakError: AppError, data: Any? = null): Nothing {
            throwError(breakError, data)
        }

        // TODO: it should be private, I don't want it to be exposed outside of this class
        inline fun <reified T> withCatchingErrors(
            onError: (error: Resource.Error<T>) -> Unit,
            flowImpl: () -> Unit
        ) {
            try {
                flowImpl()
            } catch (e: AppErrorAsThrowable) {
                if (e.data is T?) {
                    onError(Resource.Error(e.error, e.data as T?))
                } else {
                    Logger.wtf(
                        "FlowUtils::withCatchingErrors",
                        "wrong parameterized type inside the Resource.Error object that is a part of the Throwable thrown, it shouldn't happen at all!",
                        e
                    )
                    onError(Resource.Error(e.error))
                }
            }
        }

        private fun throwError(error: AppError, data: Any? = null): Nothing {
            throw AppErrorAsThrowable.create(error, data)
        }
    }
}

Я пытался сделать withCatchingErrors() приватным, но это заставляет меня сделать и метод withBreakableFlow() приватным — а это, очевидно, не то, чего я хочу. withBreakableFlow() должен быть общедоступным методом.

Вопрос в том, как инкапсулировать метод withCatchingErrors(), не делая withBreakableFlow() приватным?

Заранее благодарим вас за любую поддержку.

Я не изучал ваш код подробно, поэтому, возможно, я что-то упускаю, но почему вы не можете просто поместить реализацию withCatchingErrors в функцию withBreakableFlow? У вас есть доступ к одному и тому же материализованному T во втором, и оба принимают одни и те же аргументы (вы буквально просто пересылаете аргументы от последнего к первому).

Slaw 09.06.2024 16:59

@Slaw withCatchingErrors — это «внутренний» метод, его название лучше описывает реализацию. Однако withBreakableFlow имеет больше смысла для тех, кто использует его при написании реализации для построителя потоков и не заинтересован в реализации «под капотом». Обычно вся проблема связана с именованием

K0cur 09.06.2024 17:50

Тогда я думаю ответ Джоффри — лучшее решение для вас. Но я также хочу отметить, что иногда можно «нарушать правила», особенно когда принуждение себя следовать определенным передовым практикам делает реализацию кода более сложной, чем она того стоит. Если вы не используете и не планируете использовать withCatchingErrors в других функциях, возможно, это один из таких случаев. Вы всегда можете добавить комментарии к коду, если считаете, что реализация требует объяснения.

Slaw 10.06.2024 08:43
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
3
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Перво-наперво, если вы можете согласиться на видимость internal, вы можете сделать withCatchingErrors внутренним и использовать @PublishedApi, чтобы позволить встроенным функциям использовать его, несмотря на его меньшую видимость.

Однако вы должны понимать, что функция по-прежнему технически общедоступна и встроена в код потребителей, а это означает, что на нее распространяются требования двоичной совместимости и т. д.

Возможно, разумнее избегать @PublishedApi в зависимости от вашего варианта использования.

Однако не вся надежда потеряна. Хотя вы не можете выполнять или выполнять проверки is для общего T без reified, вы можете выполнять такого рода проверки с помощью отражения, если вам требуется параметр KClass<T> (см. KClass.isInstance).

Общая идея состоит в том, чтобы использовать inline+reified для удобства поверх общедоступного API с помощью KClass. Тогда ваша перегрузка inline с параметром типа reified может получить класс, используя T::class (благодаря реификации), а затем делегировать перегрузке, принимая KClass.

В вашем случае это может выглядеть примерно так:

inline fun <reified E : Any, R> FlowCollector<R>.withBreakableFlow(
    noinline onBreakError: (breakError: Resource.Error<E>) -> Unit,
    noinline flowImpl: () -> Unit
) {
    withBreakableFlow(E::class, onBreakError, flowImpl)
}

fun <E : Any, R> FlowCollector<R>.withBreakableFlow(
    errorType: KClass<E>,
    onBreakError: (breakError: Resource.Error<E>) -> Unit,
    flowImpl: () -> Unit
) {
    withCatchingErrors(errorType, onBreakError, flowImpl)
}

private fun <E : Any> withCatchingErrors(
    errorType: KClass<E>,
    onError: (error: Resource.Error<E>) -> Unit,
    flowImpl: () -> Unit
) {
    try {
        flowImpl()
    } catch (e: AppErrorAsThrowable) {
        if (errorType.isInstance(e.data)) {
            onError(Resource.Error(e.error, e.data as E?))
        } else {
            Logger.wtf(
                "FlowUtils::withCatchingErrors",
                "wrong parameterized type inside the Resource.Error object that is a part of the Throwable thrown, it shouldn't happen at all!",
                e
            )
            onError(Resource.Error(e.error))
        }
    }
}

Другие вопросы по теме