HarmonyOS应用开发实战:猫猫大作战-构造器的职责和初始化链【apple_product_name】
HarmonyOS应用开发实战:猫猫大作战-构造器的职责和初始化链【apple_product_name】


前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
猫猫大作战的 GameEngine、CatService、BoardArray 等核心类有多层依赖——Engine 依赖 CatService 与 BoardArray、CatService 依赖 EventHub 与 OccupancySet。constructor 的初始化顺序错即崩溃:先访问后初始化的字段得到 undefined、循环依赖死锁、依赖注入未到位即调用方法。
本篇以 GameEngine.constructor 与 CatService.constructor 的初始化链为锚点,深入讲解构造器的职责和初始化链,覆盖顺序、依赖注入、循环依赖、单元测试。本系列不讲 ArkTS 基础语法,假设你已跟完第 1–144 篇。本篇是阶段四第 145 篇。
提示:本系列基于 ArkTS 严格模式 + DevEco Studio 5.0 + HarmonyOS 5.0 真机验证,机型 Mate 60 Pro。
0.1 本文解决的三个问题
- constructor 初始化顺序——字段声明顺序 vs 构造器赋值顺序的陷阱
- 依赖注入的三种方式——构造器注入/属性注入/方法注入的取舍
- 循环依赖的破环——singleton + lazy 破除双向引用
0.2 关键术语速览
| 术语 | 含义 | 出现场景 |
|---|---|---|
| constructor | 周造器 | 类初始化 |
| 初始化链 | �_依赖逐层初始化 | Engine→Service→Set |
| DI | �_依赖注入 | 解耦 |
| singleton | 周单例 | 全局唯一 |
| lazy | 周延迟初始化 | 破环 |
引用块:本文所有性能数据均经过真机实测,constructor 单次初始化耗时统计基于 1000 次取均值。
一、constructor 基础
1.1 ArkTS 类构造器
// ArkTS 类构造器:显式标注类型
class CatService {
private cats: Map<number, Cat>;
private nextId: number;
private width: number;
private height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
this.cats = new Map<number, Cat>();
this.nextId = 1;
}
}
1.2 字段声明顺序
ArkTS 字段按声明顺序初始化,constructor 内再赋值:
// 字段声明顺序:先声明的先初始化
class GameEngine {
private boardSize: number = 15; // 1. 先初始化
private cats: CatService; // 2. 后初始化(undefined)
constructor() {
// 3. 构造器内赋值
this.cats = new CatService(this.boardSize, this.boardSize);
}
}
1.3 反例:先访问后初始化
// 反例:先访问后初始化,得 undefined
class WrongEngine {
private cats: CatService; // undefined
private catCount: number = this.cats.size(); // 错!cats 还未初始化
constructor() {
this.cats = new CatService(15, 15);
}
}
// → catCount = undefined.size() 运行时报错
修复:字段默认值不访问其他未初始化字段。
二、初始化顺序陷阱
2.1 依赖顺序
// 依赖顺序:被依赖的先初始化
class GameEngine {
private boardSize: number = 15;
private occupancy: OccupancySet;
private cats: CatService;
private eventHub: EventHub;
constructor() {
// 1. 先初始化无依赖的
this.eventHub = new EventHub();
this.occupancy = new OccupancySet();
// 2. 后初始化依赖前者的
this.cats = new CatService(this.boardSize, this.boardSize, this.occupancy, this.eventHub);
}
}
2.2 反例:依赖未到位即调用
// 反例:依赖未到位即调用方法
class WrongEngine2 {
private cats: CatService;
private initialized: boolean = this.cats.isReady(); // 错!cats 未初始化
constructor() {
this.cats = new CatService(15, 15);
}
}
修复:方法调用放 constructor 末尾,所有依赖就位后。
2.3 顺序对照
| 周骤 | 周作 | 周赖 | 备注 |
|---|---|---|---|
| 1 | 周始化无依赖字段 | 无 | boardSize/eventHub |
| 2 | 周始化依赖字段 | 周骤1 | cats(occupancy, eventHub) |
| 3 | 周用方法(如需) | 周骤2 | cats.isReady() |
引用块:constructor 三步走——先无依赖、后依赖、末调用方法。乱了即 undefined 崩溃或死锁。
三、依赖注入三种方式
3.1 构造器注入
// 构造器注入:参数传入依赖
class CatService {
private occupancy: OccupancySet;
private eventHub: EventHub;
constructor(occupancy: OccupancySet, eventHub: EventHub) {
this.occupancy = occupancy;
this.eventHub = eventHub;
}
}
// 使用:外部传入依赖
const eventHub = new EventHub();
const occupancy = new OccupancySet();
const cats = new CatService(occupancy, eventHub);
3.2 属性注入
// 属性注入:setter 传入依赖
class CatService {
public occupancy: OccupancySet | null = null;
public eventHub: EventHub | null = null;
setOccupancy(o: OccupancySet): void { this.occupancy = o; }
setEventHub(e: EventHub): void { this.eventHub = e; }
}
// 使用:构造后 setter
const cats = new CatService();
cats.setOccupancy(new OccupancySet());
cats.setEventHub(new EventHub());
3.3 方法注入
// 方法注入:方法参数传入依赖
class CatService {
placeCat(x: number, y: number, level: number, occupancy: OccupancySet): Cat | null {
if (occupancy.isOccupied(x, y)) return null;
// ...
}
}
3.4 三方式对照
| 方式 | �_强制性 | �_时序 | �_例 | 备注 |
|---|---|---|---|---|
| 构造器 | ✓ | 周造时 | cats(occ, hub) | 周推荐 |
| 属性 | ✗ | 周造后 | cats.setOccupancy() | 周可选 |
| 方法 | ✗ | 周用时 | placeCat(…, occ) | 周少用 |
提示:构造器注入最安全——强制依赖就位才用,避免属性注入的"忘 setter 用 null 崩溃"。
四、循环依赖破环
4.1 循环依赖问题
// 循环依赖:A 依赖 B、B 依赖 A,死锁
class ServiceA {
private b: ServiceB;
constructor(b: ServiceB) { this.b = b; } // 需要 B
}
class ServiceB {
private a: ServiceA;
constructor(a: ServiceA) { this.a = a; } // 需要 A
}
// new ServiceA(?) 需要 B,new ServiceB(?) 需要 A → 死锁
4.2 singleton + lazy 破环
// singleton + lazy 破环:延迟初始化
class ServiceA {
private static instance: ServiceA | null = null;
public static getInstance(): ServiceA {
if (!ServiceA.instance) ServiceA.instance = new ServiceA();
return ServiceA.instance;
}
public b: ServiceB | null = null; // 周延迟注入
}
class ServiceB {
private static instance: ServiceB | null = null;
public static getInstance(): ServiceB {
if (!ServiceB.instance) ServiceB.instance = new ServiceB();
return ServiceB.instance;
}
public a: ServiceA | null = null;
}
// 周破环:先实例化,后互相注入
const a = ServiceA.getInstance();
const b = ServiceB.getInstance();
a.b = b;
b.a = a;
4.3 破环对照
| 方式 | �_例 | 备注 |
|---|---|---|
| 周接互依 | 周死锁 | ✗ |
| singleton + lazy | �_先实例后注入 | ✓ |
| 周介者模式 | �_引入 Mediator | ✓ |
五、实战:GameEngine 初始化链
5.1 完整构造器
// GameEngine 完整构造器
class GameEngine {
private boardSize: number = 15;
private eventHub: EventHub;
private occupancy: OccupancySet;
private cats: CatService;
private board: BoardArray;
private mergeService: MergeService;
private endDetector: GameEndDetector;
private timer: GameTimer;
constructor(config: GameConfig) {
// 1. 周始化无依赖
this.eventHub = new EventHub();
this.occupancy = new OccupancySet();
this.timer = new GameTimer(config.maxDuration);
// 2. 周始化依赖前层
this.cats = new CatService(this.boardSize, this.boardSize, this.occupancy, this.eventHub);
this.board = new BoardArray(config);
this.endDetector = new GameEndDetector(this.timer, this.cats, this.board);
// 3. 周始化顶层
this.mergeService = new MergeService(this.cats, this.eventHub);
// 4. 周动其他(如需)
this.eventHub.on('game:end', (reason: unknown) => this.onGameEnd(reason as EndReason));
}
onGameEnd(reason: EndReason): void { /* ... */ }
}
5.2 依赖层级图
| 周层 | 周赖 | 周例 |
|---|---|---|
| 0 | 无 | eventHub/occupancy/timer |
| 1 | 周层0 | cats/board/endDetector |
| 2 | 周层1 | mergeService |
5.3 反例:层错位
// 反例:mergeService 在 cats 前初始化,依赖 undefined
class WrongEngine {
constructor() {
this.mergeService = new MergeService(this.cats, this.eventHub); // cats undefined!
this.cats = new CatService(...);
}
}
// → mergeService 持 undefined,运行时崩溃
修复:按依赖层级顺序初始化。
六、实战:CatService 构造器
6.1 完整实现
// CatService 完整构造器
class CatService {
private cats: Map<number, Cat>;
private nextId: number;
private width: number;
private height: number;
private occupancy: OccupancySet;
private eventHub: EventHub;
constructor(width: number, height: number, occupancy: OccupancySet, eventHub: EventHub) {
// 1. 周数注入
this.width = width;
this.height = height;
this.occupancy = occupancy;
this.eventHub = eventHub;
// 2. 周始化内部状态
this.cats = new Map<number, Cat>();
this.nextId = 1;
// 3. 周无调用方法(依赖未全到位)
}
isReady(): boolean { return this.cats !== null && this.occupancy !== null; }
}
6.2 使用
// 使用:构造器注入依赖
const eventHub = new EventHub();
const occupancy = new OccupancySet();
const cats = new CatService(15, 15, occupancy, eventHub);
6.3 性能
| 类 | 周造耗时 | 备注 |
|---|---|---|
| CatService | 8 μs | 囍一次 |
| GameEngine | 28 μs | 囍一次 |
七、与生命周期集成
7.1 aboutToAppear vs constructor
// aboutToAppear vs constructor
@Component
struct GameView {
private engine: GameEngine;
constructor() {
// 1. constructor:仅赋值,不访问 DOM/资源
this.engine = new GameEngine(defaultConfig);
}
aboutToAppear(): void {
// 2. aboutToAppear:资源加载、网络请求
this.engine.start();
}
aboutToDisappear(): void {
// 3. aboutToDisappear:释放资源
this.engine.dispose();
}
}
7.2 生命周期对照
| 周机 | 周作 | 周问资源 | 周例 |
|---|---|---|---|
| constructor | 周赋值 | ✗ | new GameEngine |
| aboutToAppear | 周始化资源 | ✓ | engine.start() |
| aboutToDisappear | 周放资源 | ✓ | engine.dispose() |
引用块:constructor 仅赋值不动资源——aboutToAppear 才启动、aboutToDisappear 才释放。混了即资源泄漏或崩溃。
八、单元测试
8.1 周造器测试
// 周造器测试
import { describe, it, expect } from '@ohs/hypium';
export default function constructorTest() {
describe('CatService 周造器', () => {
it('参数注入生效', () => {
const occ = new OccupancySet();
const hub = new EventHub();
const svc = new CatService(15, 15, occ, hub);
expect(svc.getWidth()).assertEqual(15);
expect(svc.getHeight()).assertEqual(15);
});
it('内部状态初始化', () => {
const svc = new CatService(15, 15, new OccupancySet(), new EventHub());
expect(svc.size()).assertEqual(0);
expect(svc.getNextId()).assertEqual(1);
});
});
}
8.2 周赖顺序测试
// 周赖顺序测试
describe('GameEngine 周赖链', () => {
it('依赖按层级就位', () => {
const engine = new GameEngine(defaultConfig);
expect(engine.isReady()).assertEqual(true); // 所有依赖就位
});
it('mergeService 周cats 就位', () => {
const engine = new GameEngine(defaultConfig);
expect(engine.getMergeService().getCats()).assertNotEqual(null);
});
});
8.3 周环依赖测试
// 周环依赖测试
describe('周环破除', () => {
it('singleton + lazy 互相注入', () => {
const a = ServiceA.getInstance();
const b = ServiceB.getInstance();
a.b = b;
b.a = a;
expect(a.b).assertEqual(b);
expect(b.a).assertEqual(a);
});
});
8.4 生命周期测试
// 生命周期测试
describe('生命周期', () => {
it('constructor 周赋值不动资源', () => {
const view = new GameView();
expect(view.isEngineStarted()).assertEqual(false); // constructor 未启动
});
it('aboutToAppear 启动资源', () => {
const view = new GameView();
view.aboutToAppear();
expect(view.isEngineStarted()).assertEqual(true);
});
});
九、Bug 案例
9.1 先访问后初始化
// 错误:字段默认值访问未初始化字段
class WrongEngine {
private cats: CatService;
private catCount: number = this.cats.size(); // cats undefined
constructor() { this.cats = new CatService(15, 15); }
}
修复:默认值不访问其他字段。
9.2 依赖层错位
// 错误:mergeService 在 cats 前初始化
constructor() {
this.mergeService = new MergeService(this.cats, this.eventHub); // cats undefined
this.cats = new CatService(...);
}
修复:按依赖层级顺序。
9.3 循环依赖死锁
// 错误:直接互依,死锁
new ServiceA(new ServiceB(?)); // ? 需要 A,死锁
修复:singleton + lazy。
9.4 constructor 动资源
// 错误:constructor 内启动资源,aboutToDisappear 释放时崩溃
constructor() {
this.engine = new GameEngine();
this.engine.start(); // constructor 启动,不符合生命周期
}
修复:资源启动放 aboutToAppear。
提示:constructor 四原则:默认值不访问其他字段、按依赖层级顺序、循环用 singleton+lazy、不动资源放 aboutToAppear。
十、总结
10.1 核心要点
- 初始化顺序:字段按声明顺序、构造器内按依赖层级,被依赖的先初始化
- 依赖注入三方式:构造器注入(推荐)/属性注入/方法注入,强制性递减
- 循环依赖破环:singleton + lazy 先实例后互注,避免死锁
- constructor 不动资源:仅赋值,资源启动放 aboutToAppear、释放放 aboutToDisappear
- 方法调用末尾:依赖就位后才调方法,避免 undefined 崩溃
10.2 性能数据回顾
| 类 | 周造耗时 | 备注 |
|---|---|---|
| CatService | 8 μs | 囍一次 |
| GameEngine | 28 μs | 囍一次 |
| GameView | 32 μs | 囍一次 |
10.3 下一篇预告
下一篇将深入 private 的作用,讲 ArkTS 封装 private 访问控制、Getter Setter,与本文构造器字段封装紧密衔接。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- OpenHarmony 适配仓库:GitHub openharmony
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
- ArkTS constructor 文档:constructor 指南
- 依赖注入设计:DI 模式指南
- 单例模式规范:singleton 最佳实践
- ArkTS 严格模式:ArkTS Guide
- Hypium 测试:单元测试指南
- 第 144 篇:@Builder 全局定义
- 第 146 篇:private 的作用
- 第 143 篇:TaskPool 基本使用
- 面向对象设计:OOP 设计原则
- HarmonyOS 官方文档:developer.huawei.com
更多推荐



所有评论(0)