Я написал класс 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() приватным?
Заранее благодарим вас за любую поддержку.
@Slaw withCatchingErrors — это «внутренний» метод, его название лучше описывает реализацию. Однако withBreakableFlow имеет больше смысла для тех, кто использует его при написании реализации для построителя потоков и не заинтересован в реализации «под капотом». Обычно вся проблема связана с именованием
Тогда я думаю ответ Джоффри — лучшее решение для вас. Но я также хочу отметить, что иногда можно «нарушать правила», особенно когда принуждение себя следовать определенным передовым практикам делает реализацию кода более сложной, чем она того стоит. Если вы не используете и не планируете использовать withCatchingErrors в других функциях, возможно, это один из таких случаев. Вы всегда можете добавить комментарии к коду, если считаете, что реализация требует объяснения.





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