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

文章配图:构造器的职责和初始化链
页面预览

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

猫猫大作战的 GameEngine、CatService、BoardArray 等核心类有多层依赖——Engine 依赖 CatService 与 BoardArray、CatService 依赖 EventHub 与 OccupancySet。constructor 的初始化顺序错即崩溃:先访问后初始化的字段得到 undefined、循环依赖死锁、依赖注入未到位即调用方法。

本篇以 GameEngine.constructorCatService.constructor 的初始化链为锚点,深入讲解构造器的职责和初始化链,覆盖顺序、依赖注入、循环依赖、单元测试。本系列不讲 ArkTS 基础语法,假设你已跟完第 1–144 篇。本篇是阶段四第 145 篇。

提示:本系列基于 ArkTS 严格模式 + DevEco Studio 5.0 + HarmonyOS 5.0 真机验证,机型 Mate 60 Pro。

0.1 本文解决的三个问题

  1. constructor 初始化顺序——字段声明顺序 vs 构造器赋值顺序的陷阱
  2. 依赖注入的三种方式——构造器注入/属性注入/方法注入的取舍
  3. 循环依赖的破环——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 核心要点

  1. 初始化顺序:字段按声明顺序、构造器内按依赖层级,被依赖的先初始化
  2. 依赖注入三方式:构造器注入(推荐)/属性注入/方法注入,强制性递减
  3. 循环依赖破环:singleton + lazy 先实例后互注,避免死锁
  4. constructor 不动资源:仅赋值,资源启动放 aboutToAppear、释放放 aboutToDisappear
  5. 方法调用末尾:依赖就位后才调方法,避免 undefined 崩溃

10.2 性能数据回顾

周造耗时 备注
CatService 8 μs 囍一次
GameEngine 28 μs 囍一次
GameView 32 μs 囍一次

10.3 下一篇预告

下一篇将深入 private 的作用,讲 ArkTS 封装 private 访问控制、Getter Setter,与本文构造器字段封装紧密衔接。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐