在这里插入图片描述

社区引导(开头):欢迎加入KMP&CMP 鸿蒙社区: https://atomgit.com/CPF-KMP-CMP

摘要

本文是《为 Koin 实现依赖注入适配》的姊妹篇,聚焦 KMP&CMP 生态中另一款主流 DI 库 Kodein-DI 在 OpenHarmony 上的适配实践。文章复用与 Koin 完全相同的“三层架构”模板(语义层 / 引擎层 / 验收页),仅将语义层契约替换为 Kodein 风格:用 ArkTS 复刻 bindSingleton / bindFactory / instance() 注入关键字,实现零 @kit.* 的纯语义层;引擎层则直接复用 Koin 篇的 AbilityContext 桥。验收页通过“自增 id”直观证明 singleton 返回同一实例、factory 每次新建、instance() 能反查依赖链。文末附 ArkTS 严格模式下的踩坑小结与适配仓库地址,两篇可对读,验证同一框架适配不同 DI 库的通用性。

目录

一、为什么在 Koin 之后又写 Kodein-DI

上篇《为 Koin 实现依赖注入适配》讲了“DI 库在 OpenHarmony 上如何把 AbilityContext 桥接进图”。但 KMP 生态里 DI 并非一家独大——Kodein-DI 是另一款被广泛使用、且 API 风格与 Koin 截然不同的库:Koin 是“DSL 声明式”(module { single { } }),Kodein 则是“容器 + bind<T> with singleton/factory + instance() 注入关键字”的显式风格。

把两个 DI 库都适配一遍,价值有两层:对读者,能直观看到“同一件事(对象注册与按图取用)在鸿蒙上可以用两种写法落地”;对适配实践,能验证本系列“三层架构”模板的通用性——同一个引擎层桥(AbilityContext → PlatformContext),语义层换成 Kodein 的契约,验收页就能跑出 DI 效果。本文即沿这条线,复用与 Koin 完全相同的“展示页”基础框架,给出 Kodein 的适配代码与上屏验证。

二、版本环境

  • 跨平台框架:KMP v2.2.21 / CMP v1.9.2

  • 开发环境:DevEco Studio 26.0.0 Release;真机/模拟器 ROM 6.1+

本项目开发界面如下:
在这里插入图片描述

三、适配思路:复用三层架构,只换语义层契约

  1. 语义层(Semantic):用 ArkTS 复刻 Kodein 的核心契约——DI 构建器(bindSingleton / bindFactory)、DIContainer(get / instance,单例走缓存)。零 @kit.*。
  2. 引擎层(Engine):与 Koin 篇相同的 AbilityContext 桥,封装成可注入的 PlatformContext(偏好存储 + 包信息)。重点说明:DI 库在鸿蒙上都需要它。
  3. 验收页(Verify):用 DIContainer 注入多个服务,用“自增 id”证明 singleton 是同一实例、factory 是不同实例、instance() 能反查依赖。

四、语义层:复刻 Kodein 的 DI 契约(纯 ArkTS,零 @kit)

Kodein 的本质是“按 tag 存工厂、按 tag 取实例、单例走缓存”,并支持在工厂内用 instance() 反查依赖。我们用 ArkTS 等价复刻:

// Kodein.ets —— 语义层:复刻 Kodein 的 DI 契约
export interface Binding {
  readonly scope: 'singleton' | 'factory';
  create(di: DIContainer): Object;
}

export class DI {
  private readonly bindings: Map<string, Binding> = new Map<string, Binding>();
  bindSingleton(tag: string, factory: (di: DIContainer) => Object): void {
    this.bindings.set(tag, { scope: 'singleton', create: factory });
  }
  bindFactory(tag: string, factory: (di: DIContainer) => Object): void {
    this.bindings.set(tag, { scope: 'factory', create: factory });
  }
  get raw(): Map<string, Binding> { return this.bindings; }
}

export class DIContainer {
  private readonly di: DI;
  private readonly registry: Map<string, Object> = new Map<string, Object>();
  constructor(di: DI) { this.di = di; }

  // 对应 Kodein 的 instance() 注入关键字:在工厂内按 tag 反查依赖
  instance<T>(tag: string): T { return this.get<T>(tag); }

  get<T>(tag: string): T {
    const b: Binding | undefined = this.di.raw.get(tag);
    if (b === undefined) { throw new Error(`Kodein: 未绑定 "${tag}"`); }
    if (b.scope === 'singleton') {
      const cached: Object | undefined = this.registry.get(tag);
      if (cached !== undefined) { return cached as T; }
      const created: Object = b.create(this);
      this.registry.set(tag, created);
      return created as T;
    }
    return b.create(this) as T;
  }
}

对比 Koin:Koin 的 single / factory 是 Module 上的方法,Kodein 这里用 bindSingleton / bindFactory 表达同样的语义;instance() 是 Kodein 的标志性注入关键字,我们用 DIContainer.instance(tag) 一比一对应,让“工厂内反查依赖”读起来和 Kotlin 侧 instance() 同构。

五、引擎层:与 Koin 共用的 AbilityContext 桥

Kodein 在 Android 上靠 androidContext() 注入 Context,鸿蒙这边同样用 AbilityContext。这段代码与 Koin 篇完全一致——因为“平台能力”这件事与具体 DI 库无关:

// OhosKodeinEngine.ets —— 引擎层(与 Koin 共用的桥)
import { preferences } from '@kit.ArkData';
import { bundleManager } from '@kit.AbilityKit';
import { common } from '@kit.AbilityKit';

export interface PlatformContext {
  readPref(key: string): Promise<string>;
  writePref(key: string, value: string): Promise<void>;
  bundleName(): string;
}

export class OhosPlatformContext implements PlatformContext {
  private readonly ctx: common.UIAbilityContext;
  constructor(ctx: common.UIAbilityContext) { this.ctx = ctx; }
  async readPref(key: string): Promise<string> {
    const store = await preferences.getPreferences(this.ctx, 'kodein_demo');
    return (await store.get(key, '')) as string;
  }
  async writePref(key: string, value: string): Promise<void> {
    const store = await preferences.getPreferences(this.ctx, 'kodein_demo');
    await store.put(key, value); await store.flush();
  }
  bundleName(): string {
    const info = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT);
    return info.name;
  }
}

正因为桥是通用的,Kodein 篇真正要写的“差异化代码”几乎全在语义层——这也是三层架构的价值:引擎层一次写好,换库只改语义层与验收页。

六、验收页:用自增 id 证明 singleton / factory / instance

验收页建出完整 DI 图(platform 单例、greeting 单例且依赖 platform、userRepo 工厂且依赖 greeting),然后用一个模块级自增计数器给每个对象编 id,取两次比对 id 是否相同:

// KodeinDemo.ets —— 验收页(节选)
let instanceSeq: number = 0;
class Identified { readonly id: number = ++instanceSeq; }
class GreetingService extends Identified {
  constructor(private readonly platform: PlatformContext) { super(); }
  greet(name: string): string { return `Hello, ${name}! (bundle: ${this.platform.bundleName()}) [svc#${this.id}]`; }
}
class UserRepository extends Identified {
  constructor(private readonly greeting: GreetingService) { super(); }
}

function buildGraph(ctx: common.UIAbilityContext): DIContainer {
  const di = new DI();
  di.bindSingleton('platform', () => new OhosPlatformContext(ctx));
  di.bindSingleton('greeting', (c) => new GreetingService(c.instance<PlatformContext>('platform')));
  di.bindFactory('userRepo', (c) => new UserRepository(c.instance<GreetingService>('greeting')));
  return new DIContainer(di);
}

点开页面,验证结果直接上屏:
在这里插入图片描述

在这里插入图片描述

  • greeting 单例:取两次 id=1 / id=1 → 同一实例(单例生效)
  • userRepo 工厂:取两次 id=2 / id=3 → 不同实例(工厂生效)
  • welcome 输出:Hello, User! (bundle: 你的包名) [svc#1] [repo#2]

id 一致证明单例缓存生效,id 不同证明工厂每次新建,instance() 把 platform 注入进 greeting、再把 greeting 注入进 userRepo 的依赖链完整跑通——Kodein 风格的 DI 在真机/模拟器真实生效(运行截图请见文末提交要求)。

七、踩坑小结(ArkTS 严格模式)

  1. 构造器不能带可见性修饰符:constructor(private readonly x) 被禁,必须普通参数 + 显式 this.x = x + 在子类里 super() 后赋值。
  2. 子类构造 super() 必须第一句:ArkTS 严格要求 super() 先于任何 this 访问,Identified 的字段初始化在 super() 内完成。
  3. 单例缓存用 Map 手动管:避免 ArkTS 对顶层可变状态的过度限制,与 Koin 同款做法。
  4. instance() 只是 get() 的别名:Kodein 的注入关键字在 ArkTS 侧用方法别名最干净,避免引入额外泛型魔法。
  5. AbilityContext 不能在模块顶层取:必须等组件就绪后用 this.getUIContext().getHostContext() 传入 buildGraph。

八、适配产出与地址

  • 适配仓库(AtomGit):https://atomgit.com/oh-tpc/kodein
  • 与 Koin 共用“三层架构”模板:语义层零 @kit.*,引擎层桥 @kit.ArkData + @kit.AbilityKit;两篇可对读,验证同一框架适配不同 DI 库的通用性。

社区引导(结尾):欢迎加入KMP&CMP 鸿蒙社区: https://atomgit.com/CPF-KMP-CMP

如果你在适配过程中用到了 AI 辅助工具,推荐码道(AtomCode)专属通道:

https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgiths

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐