文章目录前言什么是依赖注入Service Locator 模式定义 Service 接口注册 Service真实实现和 Mock 实现在 ArkUI 组件中使用基于 AppStorage 的进阶方案跨模块解耦实战一些选择上的建议前言鸿蒙项目写大了之后你会发现一个头疼的问题Service 层之间互相依赖组件里面到处是new XxxService()想换个 Mock 实现做测试要改一堆代码。这就是典型的耦合问题而依赖注入DI就是专门解决它的。ArkTS 没有 Spring 那种注解驱动的 DI 框架反射能力也有限所以我们得自己搞一套轻量级的方案。这篇文章从 Service Locator 到基于 AppStorage 的注入一步步来。什么是依赖注入简单说就是组件不自己创建依赖而是从外面传进来。不用 DI 的写法Componentstruct OrderPage{privateorderServicenewOrderService();// 直接 new写死了privateuserServicenewUserService();// 又 new 一个asyncloadOrders(){constuserawaitthis.userService.getCurrentUser();this.ordersawaitthis.orderService.getOrdersByUser(user.id);}}问题很明显OrderPage和具体的OrderService、UserService实现绑死了。想换成MockOrderService做 UI 测试改代码吧。Service Locator 模式最简单的方式是搞一个全局注册表Service 在里面注册用的时候从里面取。虽然不是严格意义上的 DI但效果差不多实现成本也最低。exportclassServiceLocator{privatestaticservices:Mapstring,ObjectnewMap();privatestaticfactories:Mapstring,()ObjectnewMap();// 注册单例staticregisterT(name:string,instance:T):void{this.services.set(name,instanceasObject);}// 注册工厂每次调用 get 都创建新实例staticregisterFactoryT(name:string,factory:()T):void{this.factories.set(name,factoryas()Object);}// 注册延迟单例第一次 get 时才创建staticregisterLazyT(name:string,factory:()T):void{this.factories.set(name,(){constinstancefactory()asObject;this.services.set(name,instance);this.factories.delete(name);// 后续直接从 services 取returninstance;});}// 获取服务staticgetT(name:string):T{constservicethis.services.get(name);if(service)returnserviceasT;constfactorythis.factories.get(name);if(factory)returnfactory()asT;thrownewError(Service ${name} 未注册请检查 ServiceLocator 初始化);}// 检查是否已注册statichas(name:string):boolean{returnthis.services.has(name)||this.factories.has(name);}// 清除所有注册测试用staticclear():void{this.services.clear();this.factories.clear();}}定义 Service 接口关键一步用接口而不是具体类来注册。这样业务代码只依赖接口实现随便换。// 用户服务接口exportinterfaceIUserService{getCurrentUser():PromiseUserInfo;login(username:string,password:string):PromiseLoginResult;logout():Promisevoid;}// 订单服务接口exportinterfaceIOrderService{getOrdersByUser(userId:string):PromiseOrder[];createOrder(items:OrderItem[]):PromiseOrder;cancelOrder(orderId:string):Promiseboolean;}// 商品服务接口exportinterfaceIProductService{getProducts(category?:string):PromiseProduct[];getProductDetail(id:string):PromiseProduct;searchProducts(keyword:string):PromiseProduct[];}// 服务名常量避免字符串满天飞exportconstServiceNames{UserService:UserService,OrderService:OrderService,ProductService:ProductService,}asconst;注册 Service在应用启动时把所有 Service 注册好exportdefaultclassEntryAbilityextendsUIAbility{onCreate(want:Want,launchParam:AbilityConstant.LaunchParam):void{this.registerServices();}privateregisterServices():void{// 注册真实实现ServiceLocator.registerLazyIUserService(ServiceNames.UserService,()newUserServiceImpl(httpService));ServiceLocator.registerLazyIOrderService(ServiceNames.OrderService,()newOrderServiceImpl(httpService));ServiceLocator.registerLazyIProductService(ServiceNames.ProductService,()newProductServiceImpl(httpService));Logger.info(DI,所有服务已注册);}}真实实现和 Mock 实现同一套接口生产用真实实现测试用 Mock// 真实实现exportclassUserServiceImplimplementsIUserService{constructor(privatehttp:HttpService){}asyncgetCurrentUser():PromiseUserInfo{returnawaitthis.http.getUserInfo(/user/me);}asynclogin(username:string,password:string):PromiseLoginResult{returnawaitthis.http.postLoginResult(/auth/login,{username,password});}asynclogout():Promisevoid{awaitthis.http.post(/auth/logout);}}// Mock 实现预览/测试用exportclassMockUserServiceimplementsIUserService{asyncgetCurrentUser():PromiseUserInfo{return{id:mock_001,name:测试用户,avatar:,level:5};}asynclogin(username:string,password:string):PromiseLoginResult{return{token:mock_token,userId:mock_001};}asynclogout():Promisevoid{// do nothing}}切换的时候只需要改注册代码// 开发/预览模式用 Mockif(__DEV__){ServiceLocator.registerIUserService(ServiceNames.UserService,newMockUserService());}在 ArkUI 组件中使用在组件里通过 ServiceLocator 获取服务不直接 newComponentstruct OrderListPage{Stateorders:Order[][];Stateloading:booleantrue;Stateerror:string;privateorderService:IOrderServiceServiceLocator.getIOrderService(ServiceNames.OrderService);privateuserService:IUserServiceServiceLocator.getIUserService(ServiceNames.UserService);asyncaboutToAppear(){try{this.loadingtrue;constuserawaitthis.userService.getCurrentUser();this.ordersawaitthis.orderService.getOrdersByUser(user.id);}catch(e){this.error加载订单失败;Logger.error(OrderList,加载失败,{error:String(e)});}finally{this.loadingfalse;}}build(){if(this.loading){LoadingProgress().width(48).height(48)}elseif(this.error){Text(this.error).fontColor(#FF0000)}else{List(){ForEach(this.orders,(order:Order){ListItem(){OrderCard({order})}})}}}}基于 AppStorage 的进阶方案如果你觉得 ServiceLocator 不够ArkUI还有一种思路是利用 AppStorage 来做注入。好处是 Service 状态变化可以触发 UI 更新exportclassDIContainer{// 初始化把所有 Service 放入 AppStoragestaticinit():void{AppStorage.setOrCreateIUserService(ServiceNames.UserService,ServiceLocator.getIUserService(ServiceNames.UserService));AppStorage.setOrCreateIOrderService(ServiceNames.OrderService,ServiceLocator.getIOrderService(ServiceNames.OrderService));AppStorage.setOrCreateIProductService(ServiceNames.ProductService,ServiceLocator.getIProductService(ServiceNames.ProductService));}}在组件中通过StorageLink注入Componentstruct ProductPage{// 从 AppStorage 注入Service 切换时组件会自动响应StorageLink(ServiceNames.ProductService)productService!:IProductService;Stateproducts:Product[][];asyncaboutToAppear(){this.productsawaitthis.productService.getProducts();}build(){List(){ForEach(this.products,(p:Product){ListItem(){ProductCard({product:p})}})}}}不过说实话Service 通常不需要响应式更新用 ServiceLocator 直接 get 就够用了。AppStorage 方案更适合那些需要运行时替换 Service 并且 UI 要跟着变的场景比如 Debug 面板切换 Mock 数据源。跨模块解耦实战Service 之间的依赖也通过接口来解耦。OrderService依赖UserService但不直接引用它而是从 ServiceLocator 取exportclassOrderServiceImplimplementsIOrderService{constructor(privatehttp:HttpService){}privategetuserService():IUserService{returnServiceLocator.getIUserService(ServiceNames.UserService);}asyncgetOrdersByUser(userId:string):PromiseOrder[]{returnawaitthis.http.getOrder[](/orders/user/${userId});}asynccreateOrder(items:OrderItem[]):PromiseOrder{constuserawaitthis.userService.getCurrentUser();returnawaitthis.http.postOrder(/orders,{userId:user.id,items:items});}asynccancelOrder(orderId:string):Promiseboolean{constresultawaitthis.http.post{success:boolean}(/orders/${orderId}/cancel);returnresult.success;}}这样OrderService和UserService的具体实现完全没有耦合。UserService换成 Mock 实现OrderService无感知。一些选择上的建议手动 DI vs 框架 DI鸿蒙的 ArkTS 对装饰器和反射的支持有限搞一套完整的注解驱动 DI 框架成本太高而且调试困难。ServiceLocator 虽然土但是简单直接100 行代码就跑通了出问题也好排查。什么时候用 DI如果你的项目就两三个页面直接 new 就行了别过度设计。但要是超过 10 个页面、多个 Service 互相依赖、需要做 Mock 测试那 DI 就很有价值了。注意循环依赖。A 依赖 BB 依赖 AServiceLocator 会死循环。用延迟初始化getter 方式获取可以打破循环但更好的做法是重新设计把公共逻辑抽出来变成 C让 A 和 B 都依赖 C。