回调函数传递方案对比.md 5.8 KB

回调函数传递方案对比

📋 当前实现(方案1:反射调用 init 方法)

实现方式

// AppInitializer.kt
val initMethod = socketIOManagerClass.getDeclaredMethod(
    "init",
    Application::class.java,
    kotlin.jvm.functions.Function0::class.java,  // isLoggedInProvider
    kotlin.coroutines.SuspendFunction0::class.java   // refreshTokenProvider
)
initMethod.invoke(null, application, isLoggedInProvider, refreshTokenProvider)

优点 ✅

  1. 初始化逻辑集中:init 方法同时完成生命周期注册和回调设置
  2. 原子性操作:一次调用完成所有初始化,不会出现部分初始化状态
  3. 符合单例模式:标准的单例初始化模式
  4. 类型安全:回调函数在编译时创建,类型明确

缺点 ❌

  1. 反射调用复杂:需要处理 SuspendFunction0 类型,反射代码复杂
  2. 可读性差:反射代码不够直观
  3. 错误处理复杂:反射异常处理需要更多代码

🔄 方案2:分步设置(先 init,再设置属性)

实现方式

// SocketIOManager.kt
fun init(app: Application) {
    // 只负责注册生命周期监听
    ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}

// AppInitializer.kt
// 1. 先调用 init(注册生命周期)
val initMethod = socketIOManagerClass.getMethod("init", Application::class.java)
initMethod.invoke(null, application)

// 2. 再设置回调属性(直接设置,不需要处理 suspend 函数类型)
val isLoggedInProviderField = socketIOManagerClass.getDeclaredField("isLoggedInProvider")
isLoggedInProviderField.isAccessible = true
isLoggedInProviderField.set(null, isLoggedInProvider)

val refreshTokenProviderField = socketIOManagerClass.getDeclaredField("refreshTokenProvider")
refreshTokenProviderField.isAccessible = true
refreshTokenProviderField.set(null, refreshTokenProvider)

优点 ✅

  1. 反射代码简单:设置属性比调用方法更简单
  2. 不需要处理 suspend 函数类型:直接设置属性,反射不需要知道 suspend 类型
  3. 灵活性高:可以随时修改回调函数
  4. 可读性好:分步操作,逻辑清晰

缺点 ❌

  1. 初始化逻辑分散:需要两步操作
  2. 可能出现部分初始化:如果第二步失败,可能出现只注册了生命周期但没设置回调的情况
  3. 需要访问私有字段:需要 setAccessible(true),可能违反封装原则
  4. 错误处理分散:需要分别处理 init 和属性设置的异常

📊 客观对比

维度 方案1:反射调用 init 方案2:分步设置属性
代码复杂度 ⭐⭐⭐ 复杂(需要处理 SuspendFunction0) ⭐⭐ 简单(直接设置属性)
可读性 ⭐⭐ 一般(反射代码不够直观) ⭐⭐⭐ 好(分步操作清晰)
类型安全 ⭐⭐⭐ 好(编译时类型检查) ⭐⭐ 一般(运行时设置)
原子性 ⭐⭐⭐ 好(一次调用完成) ⭐⭐ 一般(分步操作)
错误处理 ⭐⭐ 一般(集中处理) ⭐⭐ 一般(分散处理)
灵活性 ⭐⭐ 一般(需要重新 init) ⭐⭐⭐ 好(可随时修改)
封装性 ⭐⭐⭐ 好(通过方法调用) ⭐⭐ 一般(直接访问字段)

🎯 推荐方案

推荐:方案2(分步设置)

理由:

  1. 反射代码更简单:设置属性比调用方法简单,不需要处理复杂的 suspend 函数类型
  2. 可读性更好:分步操作逻辑清晰,易于理解和维护
  3. 实际风险低:虽然可能出现部分初始化,但在 AppInitializer 中集中处理,风险可控
  4. 符合常见模式:很多框架都是先初始化,再配置回调

改进建议:

  • 在 SocketIOManager 中添加 setCallbacks() 方法,通过方法设置而不是直接访问字段
  • 这样可以保持封装性,同时简化反射代码

💡 改进方案(推荐)

方案3:添加 setCallbacks 方法(最佳)

// SocketIOManager.kt
fun init(app: Application) {
    // 只负责注册生命周期监听
    ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}

fun setCallbacks(
    isLoggedInProvider: (() -> Boolean)? = null,
    refreshTokenProvider: (suspend () -> String?)? = null
) {
    this.isLoggedInProvider = isLoggedInProvider
    this.refreshTokenProvider = refreshTokenProvider
}

// AppInitializer.kt
// 1. 先调用 init(注册生命周期)
val initMethod = socketIOManagerClass.getMethod("init", Application::class.java)
initMethod.invoke(null, application)

// 2. 再调用 setCallbacks(设置回调)
val setCallbacksMethod = socketIOManagerClass.getMethod(
    "setCallbacks",
    kotlin.jvm.functions.Function0::class.java,  // isLoggedInProvider
    kotlin.coroutines.SuspendFunction0::class.java   // refreshTokenProvider
)
setCallbacksMethod.invoke(null, isLoggedInProvider, refreshTokenProvider)

优势:

  • ✅ 保持封装性(通过方法而不是字段)
  • ✅ 反射代码相对简单(调用方法比设置字段更标准)
  • ✅ 逻辑清晰(分步操作)
  • ✅ 类型安全(方法签名明确)

缺点:

  • ⚠️ 仍然需要处理 SuspendFunction0 类型(但比方案1简单,因为方法签名更清晰)

📝 结论

当前方案(方案1)的问题:

  • 反射调用 init 方法时,需要处理复杂的 SuspendFunction0 类型
  • 虽然功能正常,但代码复杂度较高

推荐改进:

  • 采用方案3(添加 setCallbacks 方法)
  • 或者采用方案2(分步设置属性)(如果不在意直接访问字段)

最终建议:

  • 如果追求代码简洁:使用方案2(分步设置属性)
  • 如果追求封装性:使用方案3(添加 setCallbacks 方法)