公司动态
HarmonyOS 应用开发《掌上英语》第13篇:面向对象 vs 函数式:ArkTS 中的编程范式选择
面向对象 vs 函数式ArkTS 中的编程范式选择一、引言ArkTS 是 HarmonyOS 原生开发语言它在 TypeScript 基础上增强了静态类型检查和装饰器体系同时保留了多范式的表达能力。在实际项目开发中我们经常面临一个选择用面向对象OOP还是函数式FP这个问题没有绝对答案——每种范式都有其擅长的领域。本文将以我们英语学习 App 的实际代码为例分析 class ObservedV2的面向对象模型、Builder参数化 UI 片段、以及函数式单例工具类如AudioPlayer的设计思路帮你理清在 ArkTS 中如何做出合理的范式选择。二、面向对象范式class ObservedV2 响应式模型2.1 WordCard 数据模型在 ArkTS V2 装饰器体系中ObservedV2Trace是实现响应式数据模型的标准方式。它的本质是面向对象的——通过 class 封装数据用装饰器标记可观察属性// commonLib/models/WordCardModel.etsObservedV2exportclassWordCard{Traceid:number0;Traceword:string;Tracephonetic:string;TracepartOfSpeech:string;Tracetranslation:string;Traceexample:string;TraceexampleTranslation:string;TraceaudioUrl:string;Tracetags:string[][];Tracecategory:stringbasic;constructor(id:number,word:string,phonetic:string,partOfSpeech:string,translation:string,example:string,exampleTranslation:string,audioUrl:string,tags:string[][],category:stringbasic){this.idid;this.wordword;// ... 其余字段赋值}}为什么选 class 而非 plain object在 ArkTS 中普通的对象字面量{ word: apple, ... }无法被Trace追踪。只有通过ObservedV2装饰的 class 实例其Trace属性变化才能触发 UI 刷新。这是面向对象范式在 ArkTS 响应式体系中的核心优势——class 不是可选的而是响应式的基础。2.2 ObservedV2 的工作原理当你在ComponentV2装饰的组件中读取wordCard.word时ArkTS 框架会自动建立属性→组件的依赖关系。一旦wordCard.word被修改只有读取了这个特定字段的组件会重新渲染——其他未依赖的组件不受影响。ComponentV2struct WordDisplay{ParamwordCard:WordCardnewWordCard();build(){Text(this.wordCard.word)// 只依赖 word 字段.fontSize(34);Text(this.wordCard.phonetic)// 只依赖 phonetic 字段.fontSize(15);}}这种字段级粒度的响应式追踪是面向对象方案独有的能力。如果改用函数式不可变数据每次修改都需要创建新对象并整体重新赋值性能开销大且代码更繁琐。2.3 CourseCoreBar封装业务逻辑的类除了数据模型面向对象也适合封装带有行为的状态// MainPage.etsObservedV2exportclassCourseCoreBar{TraceshowIcon:ResourceStr;Tracedescription:string;constructor(showIcon:ResourceStr,description:string){this.showIconshowIcon;this.descriptiondescription;}}这类带状态的业务对象天然适合 class——它同时承载数据和 UI 依赖的响应式属性无需在组件内部维护额外的状态管理逻辑。三、函数式范式Builder 与工具类3.1 Builder参数化 UI 片段ArkTS 的Builder是一个纯粹的 UI 函数——它接收参数返回 UI 描述没有内部状态。这正是函数式编程中纯函数的体现// CourseHomePage.etsBuildertabBuilder(label:string,key:string){Text(label).fontSize(14).fontColor(this.currentCategorykey?#165DFF:$r(sys.color.font_secondary)).fontWeight(this.currentCategorykey?FontWeight.Bold:FontWeight.Normal).padding({left:12,right:12,top:6,bottom:6});}// 在 build() 中调用TabContent(){this.cardArea();}.tabBar(this.tabBuilder(全部,all));Builder的优势在于无副作用输入相同参数输出相同 UI可组合多个Builder可以嵌套组合按需复用同一段 UI 逻辑在不同位置调用只需传不同参数对于不需要内部状态的 UI 片段用Builder而非创建一个新组件可以减少组件树的层次提升渲染性能。3.2 Builder 导出模式项目采用统一的 Builder 导出模式将每个页面的构建函数暴露给路由系统// CourseHomePage.ets 末尾BuilderexportfunctionCourseHomePageBuilder(){CourseHomePage();}这是一种工厂函数风格——CourseHomePageBuilder是一个纯函数每次调用都创建新的组件实例。路由系统通过RouterTable统一管理这些 Builder// RouterTable.etsRouterTable.builderMap.set(RouterMap.WORD_CARD_PAGE,wrapBuilder(WordCardPageBuilder));这种模式将创建组件的职责从组件内部剥离出来实现了更干净的关注点分离。3.3 AudioPlayer单例模式中的函数式设计AudioPlayer虽然使用了 class 语法但它的设计理念更接近函数式——全局唯一实例无 UI 依赖// commonLib/utils/AudioPlayer.etsexportclassAudioPlayer{privatestaticinstance?:AudioPlayer;privateavPlayer:media.AVPlayer|nullnull;privatecurrentUrl:string;privateconstructor(){}publicstaticgetInstance():AudioPlayer{if(!AudioPlayer.instance){AudioPlayer.instancenewAudioPlayer();AudioPlayer.instance.initPlayer();}returnAudioPlayer.instance;}publicplay(url:string):void{if(!url||url)return;if(!this.avPlayer)return;// ... 播放逻辑}publicstop():void{/* ... */}publicrelease():void{/* ... */}}工具类使用单例 方法的模式有几个优点无状态副作用每次调用play()只依赖传入的 URL 参数全局复用任何一个页面都可以通过AudioPlayer.getInstance().play(url)播放音频无 UI 耦合可以在非 UI 线程中使用不受组件生命周期影响类似的还有NewWordManager、GlobalContextUtils等工具类都采用同样的单例 方法设计。四、范式对比何时用哪种维度面向对象 (class)函数式 (Builder / 单例)状态管理Trace字段级追踪适合 UI 数据无状态或全局单例适合工具能力复用方式继承、组合、依赖注入参数化、高阶函数响应式原生支持属性变化自动刷新 UI不涉及响应式适合非 UI 逻辑测试难度需要 mock 状态和装饰器环境纯函数测试简单典型场景数据模型、ViewModel、业务实体UI 片段复用、工具类、路由工厂选择指南数据模型→ 用 class ObservedV2。需要响应式追踪的属性加上Trace确保 UI 自动刷新。UI 复用片段→ 用Builder。当一段 UI 只是带参数的模板且不需要独立生命周期时Builder比抽取成独立组件更轻量。工具类/服务类→ 用单例 方法。如音频播放、网络请求、数据持久化这些不依赖 UI 状态的功能适合函数式风格。组件创建工厂→ 用导出函数Builder 模式。将组件的创建与路由解耦方便路由表统一管理。五、混合范式的最佳实践实际项目中两种范式不是互斥的而是互补的。以下是我们项目中的混合使用模式// 1. 面向对象ViewModel 管理状态ObservedV2classTopicVM{TracecurrentIndex:number0;// ... 响应式属性}// 2. 函数式Builder 创建组件BuilderexportfunctionTopicPageBuilder(){TopicPage();}// 3. 工具类单例全局服务constaudioPlayerAudioPlayer.getInstance();audioPlayer.play(hello.mp3);// 4. Builder 复用 UI 片段在组件内部BuildercommonHeader(title:string){Text(title).fontSize(18).fontWeight(FontWeight.Bold);}核心原则有状态的用 class无状态的用函数。ArkTS V2 的装饰器体系让面向对象在 UI 层如鱼得水而函数式风格在工具层保持简洁和可测试性。理解两种范式的优劣在合适的场景选择合适的方式比固执于一种纯范式要实用得多。六、总结在 ArkTS 中面向对象和函数式编程并非二选一的对立关系而是一枚硬币的两面面向对象class ObservedV2/Trace是 ArkTS 响应式 UI 的基础适合数据模型、ViewModel 和业务实体函数式Builder、单例工具类在 UI 片段复用、工具服务和无状态逻辑中更简洁混合使用是最佳实践数据层用 OOP 保证响应式工具层用 FP 保证可测试性理解每种范式的设计意图和适用场景才能写出既符合 ArkTS 平台特性又易于维护的代码。