JetPack中常用的设计模式

1) Observer(观察者)

落点:LiveData / Flow + Lifecycle

动机:UI 随数据变化自动更新,并与生命周期解耦。

kotlin 复制代码
class UserVM : ViewModel() {
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> = _user
}
class ProfileFragment : Fragment(R.layout.frag) {
    private val vm: UserVM by viewModels()
    override fun onViewCreated(v: View, s: Bundle?) {
        vm.user.observe(viewLifecycleOwner) { ui -> render(ui) } // 只在 STARTED 以后回调
    }
}

要点

  • LiveData 内部会根据 Lifecycle.State 自动添加/移除 observer(避免泄漏)。
  • StateFlow/Flow 同样是观察者模型,但不关心生命周期,要配合 repeatOnLifecycle。

2) Mediator(中介者)

落点 A:MediatorLiveData(多源聚合)

落点 B:Paging 3 的 RemoteMediator(本地缓存 + 远端分页的协作"中枢")

ini 复制代码
val fullName = MediatorLiveData<String>().apply {
    addSource(firstName) { v -> value = "$v ${lastName.value}" }
    addSource(lastName) { v -> value = "${firstName.value} $v" }
}
kotlin 复制代码
@OptIn(ExperimentalPagingApi::class)
class ArticleMediator(
  private val service: Api, private val db: AppDb
) : RemoteMediator<Int, Article>() {
  override suspend fun load(loadType: LoadType, state: PagingState<Int, Article>)
      : MediatorResult {
    // 统一协调:决定请求页、下发网络、落库、告知是否 endOfPagination
  }
}

要点

  • MediatorLiveData 把多个 LiveData 的变更集中协调,避免互相观察的网状依赖。
  • RemoteMediator 把"网络页码/边界"与"本地分页源"解耦,是 Paging 3 的灵魂。

3) Repository(仓库)

落点:Google 官方架构建议(Repository 作为 UI 与数据源门面)。

动机:隐藏数据来源(DB/网络/缓存/文件),对上层暴露统一 API。

kotlin 复制代码
class UserRepo @Inject constructor(
  private val api: Api, private val dao: UserDao
) {
  fun user(id: Long): Flow<User> =
    dao.observe(id).onStart {
      val net = api.loadUser(id)
      dao.insert(net)
    }
}

要点

  • 上层(ViewModel/UI)只依赖 Repository 接口,便于测试与替换实现(Fake/Mock)。

4) Factory(工厂)

落点:ViewModelProvider.Factory / AbstractSavedStateViewModelFactory

动机:创建带参数/带 SavedStateHandle 的 ViewModel。

kotlin 复制代码
class DetailVM(private val repo: Repo, private val id: Long) : ViewModel()
class DetailVMFactory(
  owner: SavedStateRegistryOwner, private val repo: Repo, defaultArgs: Bundle?
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
  override fun <T: ViewModel> create(key: String, model: Class<T>, state: SavedStateHandle): T {
    val id = state.get<Long>("id")!!
    return DetailVM(repo, id) as T
  }
}

要点

  • 不用 Hilt 时,工厂是最标准创建路径;用 Hilt 则由 DI 容器"接管工厂"。

5) Singleton(单例)

落点:RoomDatabase、DataStore、Retrofit(虽不属 Jetpack,但常并用)

动机:昂贵对象(连接池、DB)的全局唯一实例。

kotlin 复制代码
@Database(entities = [User::class], version = 1)
abstract class AppDb : RoomDatabase() {
  companion object {
    @Volatile private var INSTANCE: AppDb? = null
    fun get(context: Context) = INSTANCE ?: synchronized(this) {
      INSTANCE ?: Room.databaseBuilder(context, AppDb::class.java, "app.db").build()
        .also { INSTANCE = it }
    }
  }
}

6) Builder(生成器)

落点 A:WorkManager 的 OneTimeWorkRequestBuilder / Constraints.Builder

落点 B:Navigation Kotlin DSL 构建图;Compose ConstraintSet 也体现 Builder 味道。

scss 复制代码
val work = OneTimeWorkRequestBuilder<UploadWorker>()
  .setConstraints(
    Constraints.Builder()
      .setRequiredNetworkType(NetworkType.UNMETERED)
      .setRequiresBatteryNotLow(true)
      .build()
  ).build()
WorkManager.getInstance(ctx).enqueue(work)

要点

  • 将复杂对象的可选参数分步构造,结尾 build() 收口,防止构造函数爆炸。

7) Strategy(策略)

落点 A:DiffUtil.ItemCallback(比较策略可插拔)

落点 B:LinearLayoutManager/GridLayoutManager(布局策略互换)

动机 :在不改调用方的情况下切换算法/行为

kotlin 复制代码
class PostDiff: DiffUtil.ItemCallback<Post>() {
  override fun areItemsTheSame(a: Post, b: Post) = a.id == b.id
  override fun areContentsTheSame(a: Post, b: Post) = a == b
}
val adapter = ListAdapter(PostDiff()) { /* ... */ }

8) Adapter(适配器)

落点:RecyclerView.Adapter / PagingDataAdapter

动机 :把任意数据源适配到统一的 ViewHolder 渲染接口。

kotlin 复制代码
class PostAdapter : ListAdapter<Post, VH>(PostDiff()) {
  override fun onCreateViewHolder(p: ViewGroup, t: Int) = VH(ItemPostBinding.inflate(...))
  override fun onBindViewHolder(h: VH, pos: Int) = h.bind(getItem(pos))
}

9) Decorator(装饰)

落点:RecyclerView.ItemDecoration / Insetter / WindowInsets 处理

动机 :在不修改原组件的前提下叠加行为/样式

kotlin 复制代码
recyclerView.addItemDecoration(object: RecyclerView.ItemDecoration() {
  override fun getItemOffsets(outRect: Rect, v: View, p: RecyclerView, s: State) {
    outRect.bottom = 8.dp
  }
})

10) Chain of Responsibility(责任链)

落点:OnBackPressedDispatcher、NestedScrolling 的 pre/post 链、WorkManager.beginWith().then() 链式任务

动机 :把请求沿链路传递,遇到能处理者即停止

scss 复制代码
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
  // 优先级高者先拦截返回键
  if (drawer.isOpen) drawer.close() else remove() /* 放行给下一个 */
}

11) Command(命令)

落点:WorkRequest(可序列化命令交给系统后台执行)、NavController.navigate(directions)

动机:把"要做的事"封装为对象,延迟/队列化执行。

kotlin 复制代码
class UploadWorker(ctx: Context, params: WorkerParameters): CoroutineWorker(ctx, params) {
  override suspend fun doWork(): Result { /* 真正的命令执行体 */ return Result.success() }
}

12) State(状态)

落点 A:SavedStateHandle、ViewModel 状态持有

落点 B:Compose 的 remember{} / rememberSaveable{} / "状态提升(state hoisting)"

动机:用不可变 UIState + 单一真相来源(SSOT)驱动 UI。

kotlin 复制代码
@HiltViewModel
class HomeVM @Inject constructor(private val saved: SavedStateHandle): ViewModel() {
  private val _ui = MutableStateFlow(UiState())
  val ui: StateFlow<UiState> = _ui.asStateFlow()
}
@Composable
fun Home(vm: HomeVM = hiltViewModel()) {
  val ui by vm.ui.collectAsState()
  Screen(ui) // 纯函数式 UI
}

13) Template Method(模板方法)

落点:DefaultLifecycleObserver / Fragment 生命周期

动机 :父类/框架定义固定流程 ,子类覆盖具体步骤

kotlin 复制代码
class LogsObserver: DefaultLifecycleObserver {
  override fun onStart(owner: LifecycleOwner) { /* 模版流程中的一步 */ }
}
lifecycle.addObserver(LogsObserver())

14) Facade(外观)

落点:NavController(对 Fragment 事务的门面)、WorkManager(对 JobScheduler/AlarmManager+Service 的门面)

动机 :用统一 API 屏蔽平台差异与复杂实现。

scss 复制代码
findNavController().navigate(
  HomeFragmentDirections.actionHomeToDetail(id = 42) // Safe Args + 单一入口
)

15) Iterator(迭代器)

落点:PagingSource → Pager → Flow<PagingData>

动机:按需迭代加载数据,UI 只"拉取下一页"。

kotlin 复制代码
class PostSource(private val api: Api): PagingSource<Int, Post>() {
  override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Post> {
    val page = params.key ?: 1
    val data = api.list(page)
    return LoadResult.Page(data, prevKey = page-1, nextKey = page+1)
  }
}
val flow = Pager(PagingConfig(pageSize = 20)) { PostSource(api) }.flow

组合范式:把模式拼起来

  • MVVM(不是 GoF,但工程架构范式) :State(Compose/LiveData/Flow) + Observer + Repository + Factory(VM 创建)+(可选)Mediator(聚合/分页中枢)。
  • 列表页面:Adapter + Strategy(DiffUtil) + Decorator(ItemDecoration) + Iterator(Paging)。
  • 导航与后台任务:Facade(NavController/WorkManager) + Command(WorkRequest) + Chain(OnBackPressed/NestedScroll)。

实战要点与坑位

  • LiveData vs Flow :UI 端想省事选 LiveData;想要背压/并发/算子选 Flow,配 repeatOnLifecycle。
  • DiffUtil 的代价 :大对象请提供稳定 id高效 equals,必要时用 AsyncDifferConfig + 自定义线程池。
  • Repository 划分:按业务边界而非技术边界拆分(如 UserRepo、FeedRepo),接口化便于测试。
  • WorkManager 设计 :可重试任务要区分幂等幂等化输入(inputData + 去重 Key);链式 then = 责任链。
  • Navigation:深链/多 back stack 用 navigation-ui-ktx + BottomNavigationView 的 setupWithNavController,避免自己手搓事务。
  • Compose 的 State:坚持"状态提升"与不可变 data class,副作用放入 LaunchedEffect/DisposableEffect,不要在 Composable 里保业务单例。

一页速记(面试可背)

  • 观察者:LiveData/Flow 监听 + 生命周期安全。

  • 中介者:MediatorLiveData 聚合;Paging 的 RemoteMediator 协调网/库。

  • 仓库:抽象数据来源,UI 只调仓库。

  • 工厂:ViewModelProvider.Factory / SavedStateFactory。

  • 单例:Room DB / Retrofit / DataStore。

  • 生成器:WorkRequest/Constraints/导航 DSL。

  • 策略:DiffUtil、LayoutManager 可替换。

  • 适配器:RecyclerView.Adapter / PagingDataAdapter。

  • 装饰:ItemDecoration/Insets。

  • 责任链:OnBackPressed/NestedScroll/Work 链。

  • 命令:WorkRequest、NavDirections。

  • 状态:SavedStateHandle、Compose state。

  • 模板方法:Lifecycle 回调骨架。

  • 外观:NavController、WorkManager。

  • 迭代器:PagingSource → PagingData。

相关推荐
我是华为OD~HR~栗栗呀4 小时前
华为OD-23届考研-Java面经
java·c++·后端·python·华为od·华为·面试
疯狂踩坑人5 小时前
【面试系列】浏览器篇
前端·面试
南北是北北5 小时前
嵌套滚动(Nested Scrolling 1/2/3)
面试
uhakadotcom5 小时前
常识:python之中的伪随机数安全风险
后端·面试·github
聪明的笨猪猪6 小时前
Java Redis “核心应用” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
晨非辰7 小时前
《超越单链表的局限:双链表“哨兵位”设计模式,如何让边界处理代码既优雅又健壮?》
c语言·开发语言·数据结构·c++·算法·面试
聪明的笨猪猪7 小时前
Java Redis “底层结构” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
绝无仅有7 小时前
面试真实经历某商银行大厂Java问题和答案总结(三)
后端·面试·github
绝无仅有7 小时前
面试真实经历某商银行大厂Java问题和答案总结(五)
后端·面试·github