ArkTS 设计模式与架构进阶:构建可维护的 HarmonyOS 应用

#

前言

在 ArkUI 开发中,代码的可维护性、可扩展性和可读性是衡量工程水平的重要标尺。HarmonyOS NEXT(API 12+)为 ArkTS 引入了丰富的装饰器和语言特性,这为在声明式 UI 框架下实现经典设计模式提供了坚实基础。

本文选取 ArkTS 中最实用的五种设计模式------Builder 模式单例模式工厂模式观察者模式策略模式------逐一剖析其核心原理,并通过完整可运行的代码示例展示每种模式在 ArkUI 项目中的落地方式。这些模式并非纸上谈兵,而是经过真实项目验证的工程实践,掌握它们可以让你的代码从"能跑"走向"优雅"。

注:本文所有代码基于 HarmonyOS NEXT / API 12+ 编写,均可直接放入 DevEco Studio 项目中运行。


一、Builder 模式:链式调用构建复杂对象

1.1 什么是 Builder 模式

在日常开发中,我们经常需要构建包含大量可选参数的对象。传统的重载构造函数会导致"构造函数爆炸",而直接传多个可选参数又会让调用方陷入"参数顺序恐惧"。Builder 模式通过链式调用(Method Chaining)优雅地解决了这个问题:调用方可以按需选择参数,代码可读性极高。

在 ArkUI 中,ArkUI 自身的 @Builder 装饰器用于声明组件构建函数,这与经典 Builder 模式有所区别------但我们可以利用这一特性结合自定义 Builder 类,实现数据对象的链式构造。

1.2 在 ArkTS 中实现经典 Builder 模式

以下示例展示如何为 UserProfileNotificationItem 这两个业务对象实现链式 Builder:

typescript 复制代码
// UserProfileBuilder.ets
// 复杂对象的链式构建器------避免构造函数爆炸

// 被构建的目标对象
class UserProfile {
  id: string = "";
  username: string = "";
  avatar: string = "";
  email: string = "";
  age: number = 0;
  bio: string = "";
  tags: string[] = [];
  isVerified: boolean = false;
  createdAt: Date = new Date();

  toString(): string {
    return JSON.stringify(this);
  }
}

// Builder 类:每个 setter 方法返回 this,支持链式调用
class UserProfileBuilder {
  private profile: UserProfile = new UserProfile();

  setId(id: string): UserProfileBuilder {
    this.profile.id = id;
    return this;
  }

  setUsername(username: string): UserProfileBuilder {
    this.profile.username = username;
    return this;
  }

  setAvatar(avatar: string): UserProfileBuilder {
    this.profile.avatar = avatar;
    return this;
  }

  setEmail(email: string): UserProfileBuilder {
    this.profile.email = email;
    return this;
  }

  setAge(age: number): UserProfileBuilder {
    this.profile.age = age;
    return this;
  }

  setBio(bio: string): UserProfileBuilder {
    this.profile.bio = bio;
    return this;
  }

  addTag(tag: string): UserProfileBuilder {
    this.profile.tags.push(tag);
    return this;
  }

  setVerified(verified: boolean): UserProfileBuilder {
    this.profile.isVerified = verified;
    return this;
  }

  setCreatedAt(date: Date): UserProfileBuilder {
    this.profile.createdAt = date;
    return this;
  }

  // 最终构建方法------返回不可变对象(使用 ES5 展开语法深拷贝)
  build(): UserProfile {
    return JSON.parse(JSON.stringify(this.profile)) as UserProfile;
  }
}

// 对外暴露的工厂方法(简化调用方代码)
function createUserProfile(): UserProfileBuilder {
  return new UserProfileBuilder();
}

export { UserProfile, UserProfileBuilder, createUserProfile };

1.3 在 ArkUI 页面中使用链式 Builder

有了 Builder 类,UI 层构建复杂对象时逻辑清晰、参数一目了然:

typescript 复制代码
// UserProfileCard.ets
import { createUserProfile, UserProfile } from './UserProfileBuilder';

// 构造一个用户档案------链式调用,参数按需填入
const developerProfile: UserProfile = createUserProfile()
  .setId("u_10086")
  .setUsername("鸿蒙开发者")
  .setAvatar("https://example.com/avatar.png")
  .setEmail("dev@harmonyos.cn")
  .setAge(28)
  .setBio("ArkUI 布道师,专注声明式 UI 架构设计")
  .addTag("ArkTS")
  .addTag("HarmonyOS")
  .addTag("Design Patterns")
  .setVerified(true)
  .setCreatedAt(new Date("2024-01-15"))
  .build();

// 在 ArkUI @Component 中展示
@Component
struct UserProfileCard {
  private profile: UserProfile = developerProfile;

  build() {
    Column() {
      Row() {
        Image(this.profile.avatar)
          .width(60)
          .height(60)
          .borderRadius(30)

        Column() {
          Text(this.profile.username)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)

          if (this.profile.isVerified) {
            Text("✓ 已认证")
              .fontSize(12)
              .fontColor("#1890ff")
          }
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ left: 12 })
      }

      Text(this.profile.bio)
        .fontSize(14)
        .fontColor("#666")
        .margin({ top: 8 })

      Row() {
        ForEach(this.profile.tags, (tag: string) => {
          Text(tag)
            .fontSize(12)
            .backgroundColor("#f0f0f0")
            .borderRadius(4)
            .padding({ left: 8, right: 8, top: 2, bottom: 2 })
            .margin({ right: 6 })
        })
      }
      .margin({ top: 8 })
    }
    .width("100%")
    .padding(16)
    .backgroundColor("#ffffff")
    .borderRadius(12)
  }
}

export { developerProfile };

1.4 小结

Builder 模式将对象的构造逻辑从构造函数中分离出来,使构造过程可读、可组合、可复用。在 ArkUI 中,当你的数据模型字段较多(超过 5 个)、且大多数字段为可选时,Builder 模式能显著提升代码质量。需要注意的是,链式 setter 会返回 this,因此调用 build() 前不要中途保存 builder 实例的引用------否则可能导致意外的引用共享问题。


二、单例模式:全局唯一状态的多重实现

2.1 为什么在 ArkUI 中需要单例

在 ArkUI 中,状态管理有多个层级:@State 管理组件内状态,@Link@Prop 处理父子数据传递。但当你需要跨越多个页面或多个无父子关系的组件共享同一份全局状态时,就需要一个全局唯一的"数据源"。这正是单例模式的用武之地。

ArkTS 中实现单例有多种路径,各有其适用场景。以下逐一展开。

2.2 方式一:@State 单例(模块级闭包)

最简洁的单例实现利用 ArkTS 的模块加载特性------模块在首次导入时初始化,导入后模块级变量在整个应用中保持唯一:

typescript 复制代码
// GlobalConfigStore.ets
// 方式一:模块级闭包单例(ArkTS 模块加载天然单例)

interface AppConfig {
  theme: string;      // "light" | "dark" | "auto"
  language: string;   // "zh-CN" | "en-US"
  fontSize: number;   // 字号缩放比例
  notificationsEnabled: boolean;
  autoSync: boolean;
}

// 默认配置
const DEFAULT_CONFIG: AppConfig = {
  theme: "auto",
  language: "zh-CN",
  fontSize: 1.0,
  notificationsEnabled: true,
  autoSync: false
};

// 模块级状态------在首次 import 时初始化,全局唯一
class AppConfigStore {
  private config: AppConfig = { ...DEFAULT_CONFIG };
  private listeners: Array<(config: AppConfig) => void> = [];

  // 获取当前配置快照
  getConfig(): AppConfig {
    return JSON.parse(JSON.stringify(this.config));
  }

  // 更新配置(深拷贝防泄漏)
  updateConfig(partial: Partial<AppConfig>): void {
    this.config = { ...this.config, ...partial };
    this.notifyListeners();
  }

  // 重置为默认配置
  reset(): void {
    this.config = { ...DEFAULT_CONFIG };
    this.notifyListeners();
  }

  // 观察者注册------配置变更通知
  subscribe(callback: (config: AppConfig) => void): void {
    this.listeners.push(callback);
  }

  // 取消观察者
  unsubscribe(callback: (config: AppConfig) => void): void {
    const idx = this.listeners.indexOf(callback);
    if (idx !== -1) {
      this.listeners.splice(idx, 1);
    }
  }

  private notifyListeners(): void {
    const snapshot = this.getConfig();
    this.listeners.forEach(cb => cb(snapshot));
  }
}

// 导出唯一实例------模块首次加载时创建
export const appConfigStore: AppConfigStore = new AppConfigStore();

2.3 方式二:LocalStorage 单例(页面级共享状态)

HarmonyOS 提供了 LocalStorage 机制,允许在页面内多个组件间共享状态。我们可以将其封装为单例模式:

typescript 复制代码
// LocalStorageSingleton.ets
// 方式二:LocalStorage 单例------适合页面内跨组件共享

import AppStorage from '@ohos.app.ability.AppStorage';

// 自定义 LocalStorage 包装器,提供类型安全的 get/set
class LocalStorageSingleton {
  private storage: LocalStorage;
  private static instance: LocalStorageSingleton | null = null;

  // 私有构造函数------禁止外部 new
  private constructor() {
    // 尝试从 AppStorage 恢复,若无则创建新的 LocalStorage
    this.storage = new LocalStorage();
  }

  // 公有静态获取实例方法------双检锁(ArkTS 为单线程,此处可省略锁)
  static getInstance(): LocalStorageSingleton {
    if (LocalStorageSingleton.instance === null) {
      LocalStorageSingleton.instance = new LocalStorageSingleton();
    }
    return LocalStorageSingleton.instance;
  }

  // 泛型安全读取
  get<T>(key: string, defaultValue?: T): T | undefined {
    return this.storage.get<T>(key) ?? defaultValue;
  }

  // 写入
  set<T>(key: string, value: T): boolean {
    return this.storage.set(key, value);
  }

  // 删除
  delete(key: string): boolean {
    return this.storage.delete(key);
  }

  // 检查是否存在
  has(key: string): boolean {
    return this.storage.has(key);
  }

  // 清除所有
  clear(): void {
    this.storage.clear();
  }
}

// 使用 AppStorage 共享到全局 UI 上下文的增强版
class GlobalAppStorage {
  private static instance: GlobalAppStorage | null = null;

  private constructor() {}

  static getInstance(): GlobalAppStorage {
    if (GlobalAppStorage.instance === null) {
      GlobalAppStorage.instance = new GlobalAppStorage();
    }
    return GlobalAppStorage.instance;
  }

  // 双向绑定数值类型
  bindNumber(key: string, value: number): Link<number> {
    return AppStorage.setAndLink(key, value);
  }

  // 双向绑定字符串
  bindString(key: string, value: string): Link<string> {
    return AppStorage.setAndLink(key, value);
  }

  // 获取可写链接
  getLink<T>(key: string): T | undefined {
    return AppStorage.get< T >(key);
  }
}

export { LocalStorageSingleton, GlobalAppStorage };

2.4 方式三:@State 属性单例(组件树内唯一状态源)

在大型 ArkUI 应用中,某些关键状态(如当前登录用户信息)需要在组件树中作为唯一可信源向下传递,此时可以将单例注入到顶层组件的 @State 中:

typescript 复制代码
// SessionStore.ets
// 方式三:在顶层 @State 中托管的单例------适合需要响应式 UI 绑定的场景

// 用户会话信息
class SessionInfo {
  userId: string = "";
  token: string = "";
  displayName: string = "";
  avatarUrl: string = "";
  role: string = "guest";
  expiresAt: number = 0; // 时间戳

  isExpired(): boolean {
    return Date.now() > this.expiresAt;
  }

  isLoggedIn(): boolean {
    return this.userId !== "" && !this.isExpired();
  }

  toString(): string {
    return `Session[${this.userId}]`;
  }
}

// 会话管理器
class SessionStore {
  private _session: SessionInfo = new SessionInfo();
  private _onSessionChange: ((session: SessionInfo) => void)[] = [];

  // 模拟登录
  login(userId: string, token: string, displayName: string): SessionInfo {
    this._session = new SessionInfo();
    this._session.userId = userId;
    this._session.token = token;
    this._session.displayName = displayName;
    this._session.expiresAt = Date.now() + 24 * 60 * 60 * 1000; // 24h
    this._notify();
    return this._session;
  }

  // 登出
  logout(): void {
    this._session = new SessionInfo();
    this._notify();
  }

  // 获取当前会话
  getSession(): SessionInfo {
    return JSON.parse(JSON.stringify(this._session));
  }

  // 获取原始引用(谨慎使用)
  getSessionRef(): SessionInfo {
    return this._session;
  }

  // 监听会话变更
  onSessionChange(callback: (session: SessionInfo) => void): void {
    this._onSessionChange.push(callback);
  }

  private _notify(): void {
    const snapshot = this.getSession();
    this._onSessionChange.forEach(cb => cb(snapshot));
  }
}

export { SessionInfo, SessionStore };

2.5 小结

三种单例实现方式各有侧重:模块级闭包单例最适合无 UI 依赖的配置中心;LocalStorage 单例适合需要在 ArkUI 中直接双向绑定的持久化数据;@State 托管单例则适合与 ArkUI 响应式系统深度集成的场景。实践中可以根据数据是否需要持久化、是否需要响应式 UI 更新来选择合适方案。


三、工厂模式:@StateFactory 与对象创建解耦

3.1 工厂模式的核心思想

工厂模式将"对象创建"与"对象使用"分离,调用方不需要知道具体类的构造细节,只需告诉工厂"我要什么",工厂负责返回合适的实例。这在 ArkUI 中有着广泛的应用场景:创建不同类型的列表项、生产不同样式的卡片组件、生成不同业务节点等。

3.2 简单工厂:类型驱动的 UI 组件工厂

以下示例展示一个通用的列表项工厂,根据数据类型动态生成对应的 UI 组件:

typescript 复制代码
// CardFactory.ets
// 简单工厂模式:根据数据类型生成不同的列表卡片

// 统一的卡片数据接口
interface CardData {
  readonly type: string;
  readonly id: string;
  readonly title: string;
  readonly subtitle?: string;
  readonly thumbnail?: string;
}

// 具体数据类型
class BannerCard implements CardData {
  readonly type: string = "banner";
  id: string;
  title: string;
  thumbnail: string;
  linkUrl: string;
  sortOrder: number = 0;

  constructor(id: string, title: string, thumbnail: string, linkUrl: string) {
    this.id = id;
    this.title = title;
    this.thumbnail = thumbnail;
    this.linkUrl = linkUrl;
  }
}

class ArticleCard implements CardData {
  readonly type: string = "article";
  id: string;
  title: string;
  subtitle: string;
  author: string;
  publishDate: string;
  thumbnail: string;
  readCount: number = 0;

  constructor(id: string, title: string, subtitle: string, author: string, thumbnail: string) {
    this.id = id;
    this.title = title;
    this.subtitle = subtitle;
    this.author = author;
    this.thumbnail = thumbnail;
    this.publishDate = new Date().toLocaleDateString("zh-CN");
  }
}

class ProductCard implements CardData {
  readonly type: string = "product";
  id: string;
  title: string;
  thumbnail: string;
  price: number;
  originalPrice: number;
  rating: number = 0;

  constructor(id: string, title: string, thumbnail: string, price: number) {
    this.id = id;
    this.title = title;
    this.thumbnail = thumbnail;
    this.price = price;
    this.originalPrice = price * 1.2;
  }
}

// 工厂类------根据类型字符串生产对应卡片数据
class CardDataFactory {
  private static registry: Map<string, (props: Record<string, Object>) => CardData> = new Map();

  // 注册创建函数到工厂
  static register(type: string, creator: (props: Record<string, Object>) => CardData): void {
    CardDataFactory.registry.set(type, creator);
  }

  // 创建卡片数据
  static create(type: string, props: Record<string, Object>): CardData | null {
    const creator = CardDataFactory.registry.get(type);
    if (creator) {
      return creator(props);
    }
    console.warn(`[CardFactory] Unknown type: ${type}`);
    return null;
  }

  // 批量创建
  static createBatch(type: string, propsList: Record<string, Object>[]): CardData[] {
    return propsList
      .map(props => CardDataFactory.create(type, props))
      .filter(card => card !== null) as CardData[];
  }
}

// 初始化内置卡片类型的注册
function initCardFactory(): void {
  CardDataFactory.register("banner", (props) =>
    new BannerCard(
      props["id"] as string,
      props["title"] as string,
      props["thumbnail"] as string,
      props["linkUrl"] as string
    )
  );

  CardDataFactory.register("article", (props) =>
    new ArticleCard(
      props["id"] as string,
      props["title"] as string,
      props["subtitle"] as string,
      props["author"] as string,
      props["thumbnail"] as string
    )
  );

  CardDataFactory.register("product", (props) =>
    new ProductCard(
      props["id"] as string,
      props["title"] as string,
      props["thumbnail"] as string,
      props["price"] as number
    )
  );
}

export { CardData, BannerCard, ArticleCard, ProductCard, CardDataFactory, initCardFactory };

3.3 在 ArkUI 中使用工厂创建列表

接下来将工厂与 @Builder 结合,实现真正的组件级工厂:

typescript 复制代码
// CardListBuilder.ets
import {
  CardData, BannerCard, ArticleCard, ProductCard,
  CardDataFactory, initCardFactory
} from './CardFactory';

// 初始化工厂
initCardFactory();

// Banner 卡片组件构建器
@Builder
function BannerCardBuilder(card: BannerCard) {
  Stack() {
    Image(card.thumbnail)
      .width("100%")
      .height(180)
      .objectFit(ImageFit.Cover)
      .borderRadius(8)

    Column() {
      Text(card.title)
        .fontSize(20)
        .fontColor("#ffffff")
        .fontWeight(FontWeight.Bold)
        .maxLines(2)
    }
    .alignItems(HorizontalAlign.Start)
    .padding(12)
    .width("100%")
    .position({ y: 100 })
    .backgroundColor("rgba(0,0,0,0.3)")
  }
  .width("100%")
  .height(180)
  .borderRadius(8)
}

// 文章卡片组件构建器
@Builder
function ArticleCardBuilder(card: ArticleCard) {
  Row() {
    Image(card.thumbnail)
      .width(100)
      .height(80)
      .borderRadius(6)
      .objectFit(ImageFit.Cover)

    Column() {
      Text(card.title)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })

      Text(card.subtitle)
        .fontSize(12)
        .fontColor("#999")
        .maxLines(1)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .margin({ top: 4 })

      Row() {
        Text(card.author)
          .fontSize(11)
          .fontColor("#666")
        Text(" · ")
          .fontSize(11)
          .fontColor("#ccc")
        Text(card.publishDate)
          .fontSize(11)
          .fontColor("#666")
      }
      .margin({ top: 4 })
    }
    .layoutWeight(1)
    .padding({ left: 10 })
    .height(80)
    .justifyContent(FlexAlign.SpaceBetween)
  }
  .width("100%")
  .padding(8)
  .backgroundColor("#ffffff")
  .borderRadius(8)
}

// 商品卡片组件构建器
@Builder
function ProductCardBuilder(card: ProductCard) {
  Column() {
    Image(card.thumbnail)
      .width("100%")
      .height(120)
      .objectFit(ImageFit.Cover)
      .borderRadius({ topLeft: 8, topRight: 8 })

    Column() {
      Text(card.title)
        .fontSize(13)
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })

      Row() {
        Text(`¥${card.price.toFixed(2)}`)
          .fontSize(15)
          .fontColor("#ff4d4f")
          .fontWeight(FontWeight.Bold)
        Text(`¥${card.originalPrice.toFixed(2)}`)
          .fontSize(11)
          .fontColor("#ccc")
          .decoration({ type: TextDecorationType.LineThrough })
          .margin({ left: 6 })
      }
      .margin({ top: 4 })
    }
    .padding(8)
    .alignItems(HorizontalAlign.Start)
  }
  .width("45%")
  .backgroundColor("#ffffff")
  .borderRadius(8)
  .margin(4)
}

// 通用卡片分发器------工厂方法 + @Builder 的完美结合
@Builder
function CardBuilder(card: CardData) {
  if (card instanceof BannerCard) {
    BannerCardBuilder(card);
  } else if (card instanceof ArticleCard) {
    ArticleCardBuilder(card);
  } else if (card instanceof ProductCard) {
    ProductCardBuilder(card);
  } else {
    //兜底未知类型
    Column() {
      Text(`Unknown card type: ${card.type}`)
        .fontSize(14)
        .fontColor("#999")
    }
    .width("100%")
  }
}

export { CardBuilder };

3.4 小结

工厂模式在 ArkUI 中的价值在于将"数据模型创建"与"UI 组件渲染"解耦。当列表中需要展示多种不同类型的内容时,工厂可以统一管理数据对象的创建逻辑,而 @Builder 则负责将每种类型渲染为对应的 UI 组件。配合 CardDataFactory.register() 的注册机制,还可以实现运行时的动态扩展------这是工厂模式与插件化思想结合的经典应用。


四、观察者模式:EventHub 自定义事件系统

4.1 观察者模式的适用场景

观察者模式(Observer Pattern)定义了一种一对多的依赖关系:当一个对象(Subject)的状态发生变化时,所有依赖它的对象(Observers)会自动收到通知并更新。在 ArkUI 中,HarmonyOS 自身大量使用了这一模式(如 @State 装饰器触发 UI 刷新)。但我们也可以构建自己的业务事件系统,用于跨组件通信、模块间解耦等场景。

4.2 通用 EventHub 实现

以下是一个完整的 EventHub 类,支持类型安全的订阅、发布、取消订阅:

typescript 复制代码
// EventHub.ets
// 通用事件中心------类型安全的观察者模式实现

type EventCallback<T = Object> = (data: T) => void;

// 事件描述接口
interface EventDescriptor {
  eventName: string;
  callback: EventCallback<Object>;
  context?: Object; // 可选:用于精确取消订阅
  once: boolean;   // 是否只触发一次
}

// 全局事件中心
class EventHub {
  private static instance: EventHub | null = null;
  private handlers: Map<string, EventDescriptor[]> = new Map();
  private oncePending: Set<string> = new Set(); // 已触发一次的回调 ID 记录

  // 私有化构造函数
  private constructor() {}

  // 获取单例
  static getInstance(): EventHub {
    if (EventHub.instance === null) {
      EventHub.instance = new EventHub();
    }
    return EventHub.instance;
  }

  // 订阅事件
  on<T = Object>(eventName: string, callback: EventCallback<T>, context?: Object): () => void {
    const descriptor: EventDescriptor = {
      eventName,
      callback: callback as EventCallback<Object>,
      context,
      once: false
    };

    const existing = this.handlers.get(eventName) || [];
    existing.push(descriptor);
    this.handlers.set(eventName, existing);

    // 返回取消订阅的函数
    return () => this.off(eventName, callback);
  }

  // 订阅一次性事件
  once<T = Object>(eventName: string, callback: EventCallback<T>, context?: Object): () => void {
    const descriptor: EventDescriptor = {
      eventName,
      callback: callback as EventCallback<Object>,
      context,
      once: true
    };

    const existing = this.handlers.get(eventName) || [];
    existing.push(descriptor);
    this.handlers.set(eventName, existing);

    return () => this.off(eventName, callback);
  }

  // 发布事件
  emit<T = Object>(eventName: string, data?: T): void {
    const descriptors = this.handlers.get(eventName) || [];

    descriptors.forEach((descriptor) => {
      // 一次性事件防止重复触发
      if (descriptor.once && this.oncePending.has(eventName + descriptor.callback.toString())) {
        return;
      }

      try {
        descriptor.callback(data as Object);

        if (descriptor.once) {
          this.oncePending.add(eventName + descriptor.callback.toString());
        }
      } catch (err) {
        console.error(`[EventHub] Error in handler for "${eventName}":`, err);
      }
    });

    // 清理已触发的一次性事件
    if (descriptors.some(d => d.once)) {
      const stillActive = descriptors.filter(d => {
        if (d.once) {
          const key = eventName + d.callback.toString();
          const wasPending = this.oncePending.has(key);
          if (wasPending) {
            this.oncePending.delete(key);
            return false;
          }
        }
        return !d.once;
      });

      if (stillActive.length === 0) {
        this.handlers.delete(eventName);
      } else {
        this.handlers.set(eventName, stillActive);
      }
    }
  }

  // 取消订阅
  off(eventName: string, callback: EventCallback<Object>): void {
    const descriptors = this.handlers.get(eventName) || [];
    const filtered = descriptors.filter(d => d.callback !== callback);

    if (filtered.length === 0) {
      this.handlers.delete(eventName);
    } else {
      this.handlers.set(eventName, filtered);
    }
  }

  // 取消某上下文中所有事件订阅
  offContext(context: Object): void {
    this.handlers.forEach((descriptors, eventName) => {
      const filtered = descriptors.filter(d => d.context !== context);
      if (filtered.length === 0) {
        this.handlers.delete(eventName);
      } else {
        this.handlers.set(eventName, filtered);
      }
    });
  }

  // 清空所有事件
  clear(): void {
    this.handlers.clear();
    this.oncePending.clear();
  }

  // 获取某事件的订阅者数量
  listenerCount(eventName: string): number {
    return this.handlers.get(eventName)?.length ?? 0;
  }
}

// 预定义事件名称常量
const AppEvents = {
  USER_LOGIN: "app:user:login",
  USER_LOGOUT: "app:user:logout",
  THEME_CHANGED: "app:theme:changed",
  LANGUAGE_CHANGED: "app:language:changed",
  NETWORK_STATUS_CHANGED: "app:network:changed",
  DATA_REFRESHED: "app:data:refreshed",
  PAGE_SHOWN: "app:page:shown",
  PAGE_HIDDEN: "app:page:hidden",
  CART_UPDATED: "app:cart:updated",
  ORDER_PLACED: "app:order:placed"
} as const;

export { EventHub, AppEvents };
export type { EventCallback, EventDescriptor };

4.3 在 ArkUI 页面中使用 EventHub

EventHub 的典型用法是在组件生命周期中订阅和取消订阅事件:

typescript 复制代码
// ProfilePage.ets
// 使用 EventHub 实现组件间的解耦通信

import { EventHub, AppEvents } from './EventHub';

// 用户登录事件数据
interface UserLoginEvent {
  userId: string;
  username: string;
  avatar: string;
  loginTime: number;
}

// 模拟用户信息更新事件
interface ProfileUpdateEvent {
  field: string;
  oldValue: string;
  newValue: string;
}

@Component
struct ProfilePage {
  @State userName: string = "游客";
  @State avatar: string = "";
  @State bio: string = "这个人很懒,什么都没写";
  @State isLoggedIn: boolean = false;
  @State notificationCount: number = 0;

  // EventHub 单例引用
  private eventHub: EventHub = EventHub.getInstance();

  // 事件处理器引用(需保存用于取消订阅)
  private onUserLoginHandler: (data: UserLoginEvent) => void = (data) => {
    this.userName = data.username;
    this.avatar = data.avatar;
    this.isLoggedIn = true;
    console.info(`[ProfilePage] 用户登录: ${data.username}`);
  };

  private onLogoutHandler: () => void = () => {
    this.userName = "游客";
    this.avatar = "";
    this.isLoggedIn = false;
    this.notificationCount = 0;
    console.info(`[ProfilePage] 用户登出`);
  };

  private onCartUpdateHandler: (count: number) => void = (count) => {
    this.notificationCount = count;
  };

  // 组件即将显示时订阅事件
  aboutToAppear(): void {
    this.eventHub.on<AppEvents>(AppEvents.USER_LOGIN, this.onUserLoginHandler);
    this.eventHub.on(AppEvents.USER_LOGOUT, this.onLogoutHandler);

    // 购物车更新事件------用 once 订阅,只触发一次后自动移除
    this.eventHub.once<number>(AppEvents.CART_UPDATED, (count) => {
      this.notificationCount = count as number;
    });

    console.info(`[ProfilePage] 已订阅 ${this.eventHub.listenerCount(AppEvents.USER_LOGIN)} 个登录监听器`);
  }

  // 组件销毁时取消订阅,防止内存泄漏
  aboutToDisappear(): void {
    this.eventHub.off(AppEvents.USER_LOGIN, this.onUserLoginHandler);
    this.eventHub.off(AppEvents.USER_LOGOUT, this.onLogoutHandler);
    // once 类型无需手动 off,已自动移除
  }

  // 模拟登录操作(发布事件)
  simulateLogin(): void {
    const loginData: UserLoginEvent = {
      userId: "u_" + Date.now(),
      username: "HarmonyDev_" + Math.floor(Math.random() * 1000),
      avatar: "https://example.com/avatars/dev.png",
      loginTime: Date.now()
    };
    this.eventHub.emit(AppEvents.USER_LOGIN, loginData);
  }

  // 模拟登出
  simulateLogout(): void {
    this.eventHub.emit(AppEvents.USER_LOGOUT);
  }

  build() {
    NavDestination() {
      Column() {
        // 头像
        if (this.avatar !== "") {
          Image(this.avatar)
            .width(80)
            .height(80)
            .borderRadius(40)
        } else {
          Column() {
            Text("访")
              .fontSize(28)
              .fontColor("#999")
          }
          .width(80)
          .height(80)
          .backgroundColor("#e8e8e8")
          .borderRadius(40)
        }

        // 用户名
        Text(this.userName)
          .fontSize(22)
          .fontWeight(FontWeight.Bold)
          .margin({ top: 12 })

        Text(this.bio)
          .fontSize(13)
          .fontColor("#999")
          .margin({ top: 4 })

        // 操作按钮
        Row() {
          if (!this.isLoggedIn) {
            Button("模拟登录")
              .onClick(() => this.simulateLogin())
              .backgroundColor("#1890ff")
              .borderRadius(8)
          } else {
            Button("模拟登出")
              .onClick(() => this.simulateLogout())
              .backgroundColor("#ff4d4f")
              .borderRadius(8)

            Button(`购物车 (${this.notificationCount})`)
              .onClick(() => {
                // 模拟购物车更新
                const newCount = this.notificationCount + 1;
                this.eventHub.emit<number>(AppEvents.CART_UPDATED, newCount);
              })
              .backgroundColor("#52c41a")
              .borderRadius(8)
              .margin({ left: 12 })
          }
        }
        .margin({ top: 20 })
      }
      .width("100%")
      .padding(24)
      .alignItems(HorizontalAlign.Center)
    }
    .title("个人中心")
  }
}

export {};

4.4 小结

EventHub 是一种轻量级的事件总线实现,它的核心价值在于解耦 :事件发布者不需要知道谁在监听,事件订阅者也不需要知道事件从何而来。在 HarmonyOS 应用中,EventHub 特别适合处理以下场景:登录状态全局同步、主题切换跨页面生效、网络状态变化通知、购物车数量实时更新等。需要特别注意的是,每次在 aboutToAppear() 中订阅事件后,必须在 aboutToDisappear() 中取消订阅,否则组件销毁后事件处理器仍在运行,将导致内存泄漏和不可预期的 UI 行为。


五、策略模式:多缓存策略的灵活切换

5.1 什么是策略模式

策略模式(Strategy Pattern)定义了一组算法(或行为),将它们各自封装为独立策略对象,并使它们可以相互替换。调用方无需了解策略的具体实现,只通过统一的接口与策略交互。在 ArkUI 中,策略模式的典型应用包括:多级缓存策略切换、图片加载策略选择、数据压缩算法切换、网络请求重试策略等。

5.2 统一缓存接口与多种策略实现

以下是完整的缓存策略系统示例,展示了如何用策略模式管理内存缓存、磁盘缓存和混合缓存:

typescript 复制代码
// CacheStrategy.ets
// 策略模式:多级缓存策略系统

// ============== 策略接口定义 ==============

interface CacheStrategy<T> {
  /** 策略名称 */
  readonly name: string;

  /** 设置缓存项 */
  set(key: string, value: T, ttlMs?: number): Promise<void>;

  /** 获取缓存项 */
  get(key: string): Promise<T | null>;

  /** 移除单个缓存项 */
  remove(key: string): Promise<void>;

  /** 清空所有缓存 */
  clear(): Promise<void>;

  /** 获取当前缓存大小(项数) */
  size(): Promise<number>;
}

// ============== 策略一:内存缓存 ==============

class MemoryCacheStrategy<T> implements CacheStrategy<T> {
  readonly name: string = "MemoryCache";

  private cache: Map<string, { value: T; expiresAt: number }> = new Map();
  private maxSize: number;
  private accessOrder: string[] = []; // LRU 顺序记录

  constructor(maxSize: number = 100) {
    this.maxSize = maxSize;
  }

  async set(key: string, value: T, ttlMs?: number): Promise<void> {
    // LRU 淘汰
    if (!this.cache.has(key) && this.cache.size >= this.maxSize) {
      const lruKey = this.accessOrder.shift();
      if (lruKey) {
        this.cache.delete(lruKey);
      }
    }

    const expiresAt = ttlMs ? Date.now() + ttlMs : Number.MAX_SAFE_INTEGER;
    this.cache.set(key, { value, expiresAt });

    // 更新 LRU 顺序
    const idx = this.accessOrder.indexOf(key);
    if (idx !== -1) {
      this.accessOrder.splice(idx, 1);
    }
    this.accessOrder.push(key);
  }

  async get(key: string): Promise<T | null> {
    const entry = this.cache.get(key);
    if (!entry) return null;

    if (Date.now() > entry.expiresAt) {
      this.cache.delete(key);
      return null;
    }

    // LRU 更新访问顺序
    const idx = this.accessOrder.indexOf(key);
    if (idx !== -1) {
      this.accessOrder.splice(idx, 1);
      this.accessOrder.push(key);
    }

    return entry.value;
  }

  async remove(key: string): Promise<void> {
    this.cache.delete(key);
    const idx = this.accessOrder.indexOf(key);
    if (idx !== -1) this.accessOrder.splice(idx, 1);
  }

  async clear(): Promise<void> {
    this.cache.clear();
    this.accessOrder = [];
  }

  async size(): Promise<number> {
    return this.cache.size;
  }
}

// ============== 策略二:磁盘缓存(模拟) ==============

class DiskCacheStrategy<T> implements CacheStrategy<T> {
  readonly name: string = "DiskCache";

  // ArkTS 中用 AppStorage 模拟磁盘持久化存储
  private prefix: string = "disk_cache_";
  private indexKey: string = "disk_cache_index";
  private ttlMs: number = 7 * 24 * 60 * 60 * 1000; // 默认 7 天

  async set(key: string, value: T, ttlMs?: number): Promise<void> {
    const fullKey = this.prefix + key;
    const entry = {
      value: JSON.stringify(value),
      expiresAt: Date.now() + (ttlMs || this.ttlMs)
    };

    AppStorage.setOrCreate(fullKey, JSON.stringify(entry));

    // 更新索引
    const indexStr = AppStorage.get<string>(this.indexKey) || "[]";
    const index: string[] = JSON.parse(indexStr);
    if (!index.includes(key)) {
      index.push(key);
      AppStorage.set(this.indexKey, JSON.stringify(index));
    }
  }

  async get(key: string): Promise<T | null> {
    const fullKey = this.prefix + key;
    const raw = AppStorage.get<string>(fullKey);
    if (!raw) return null;

    try {
      const entry = JSON.parse(raw) as { value: string; expiresAt: number };
      if (Date.now() > entry.expiresAt) {
        await this.remove(key);
        return null;
      }
      return JSON.parse(entry.value) as T;
    } catch {
      return null;
    }
  }

  async remove(key: string): Promise<void> {
    AppStorage.delete(this.prefix + key);

    const indexStr = AppStorage.get<string>(this.indexKey) || "[]";
    const index: string[] = JSON.parse(indexStr);
    const filtered = index.filter(k => k !== key);
    AppStorage.set(this.indexKey, JSON.stringify(filtered));
  }

  async clear(): Promise<void> {
    const indexStr = AppStorage.get<string>(this.indexKey) || "[]";
    const index: string[] = JSON.parse(indexStr);
    index.forEach(k => AppStorage.delete(this.prefix + k));
    AppStorage.set(this.indexKey, "[]");
  }

  async size(): Promise<number> {
    const indexStr = AppStorage.get<string>(this.indexKey) || "[]";
    return JSON.parse(indexStr).length;
  }
}

// ============== 策略三:混合缓存(Memory + Disk) ==============

class HybridCacheStrategy<T> implements CacheStrategy<T> {
  readonly name: string = "HybridCache";

  private memory: MemoryCacheStrategy<T>;
  private disk: DiskCacheStrategy<T>;
  private syncPending: Map<string, T> = new Map(); // 等待写入磁盘的增量

  constructor() {
    this.memory = new MemoryCacheStrategy<T>(200);
    this.disk = new DiskCacheStrategy<T>();
  }

  async set(key: string, value: T, ttlMs?: number): Promise<void> {
    // 同步写入内存
    await this.memory.set(key, value, ttlMs);

    // 异步写入磁盘(使用 AppStorage 模拟)
    this.disk.set(key, value, ttlMs).catch(err => {
      console.error(`[HybridCache] Disk write failed: ${err}`);
    });
  }

  async get(key: string): Promise<T | null> {
    // 先查内存
    let value = await this.memory.get(key);

    // 内存未命中则查磁盘,并回填内存
    if (value === null) {
      value = await this.disk.get(key);
      if (value !== null) {
        await this.memory.set(key, value); // 回填内存
      }
    }

    return value;
  }

  async remove(key: string): Promise<void> {
    await this.memory.remove(key);
    await this.disk.remove(key);
  }

  async clear(): Promise<void> {
    await this.memory.clear();
    await this.disk.clear();
  }

  async size(): Promise<number> {
    return await this.memory.size();
  }

  // 预热:从磁盘加载热点数据到内存
  async warmUp(keys: string[]): Promise<void> {
    for (const key of keys) {
      const value = await this.disk.get(key);
      if (value !== null) {
        await this.memory.set(key, value);
      }
    }
    console.info(`[HybridCache] Warmed up ${keys.length} keys`);
  }
}

// ============== 策略上下文:策略选择器 ==============

class CacheContext<T> {
  private strategies: Map<string, CacheStrategy<T>> = new Map();
  private currentStrategy: CacheStrategy<T>;

  constructor(defaultStrategy: CacheStrategy<T>) {
    this.currentStrategy = defaultStrategy;
  }

  // 注册策略
  registerStrategy(name: string, strategy: CacheStrategy<T>): void {
    this.strategies.set(name, strategy);
  }

  // 切换策略
  setStrategy(name: string): boolean {
    const strategy = this.strategies.get(name);
    if (!strategy) {
      console.error(`[CacheContext] Strategy "${name}" not found`);
      return false;
    }
    this.currentStrategy = strategy;
    return true;
  }

  // 获取当前策略名称
  getCurrentStrategyName(): string {
    return this.currentStrategy.name;
  }

  // 代理方法:调用当前策略的方法
  async set(key: string, value: T, ttlMs?: number): Promise<void> {
    return this.currentStrategy.set(key, value, ttlMs);
  }

  async get(key: string): Promise<T | null> {
    return this.currentStrategy.get(key);
  }

  async remove(key: string): Promise<void> {
    return this.currentStrategy.remove(key);
  }

  async clear(): Promise<void> {
    return this.currentStrategy.clear();
  }

  async size(): Promise<number> {
    return this.currentStrategy.size();
  }
}

export {
  CacheStrategy,
  MemoryCacheStrategy,
  DiskCacheStrategy,
  HybridCacheStrategy,
  CacheContext
};

5.3 缓存策略在 ArkUI 列表中的应用

以下示例展示如何通过 CacheContext 动态切换缓存策略,并应用到图片加载缓存中:

typescript 复制代码
// ImageCacheManager.ets
// 策略模式实战:图片三级缓存管理器

import {
  CacheStrategy,
  MemoryCacheStrategy,
  DiskCacheStrategy,
  HybridCacheStrategy,
  CacheContext
} from './CacheStrategy';

type CachePolicy = "memory" | "disk" | "hybrid";
type LoadedCallback = (url: string, bitmap: string) => void;

class ImageCacheManager {
  private static instance: ImageCacheManager | null = null;

  private cacheContext: CacheContext<string>;
  private loadingUrls: Set<string> = new Set(); // 防止并发重复加载
  private listeners: Map<string, LoadedCallback[]> = new Map();

  private constructor() {
    // 默认使用混合缓存策略
    const defaultStrategy = new HybridCacheStrategy<string>();
    this.cacheContext = new CacheContext<string>(defaultStrategy);

    // 注册所有可用策略
    this.cacheContext.registerStrategy("memory", new MemoryCacheStrategy<string>(300));
    this.cacheContext.registerStrategy("disk", new DiskCacheStrategy<string>());
    this.cacheContext.registerStrategy("hybrid", new HybridCacheStrategy<string>());
  }

  static getInstance(): ImageCacheManager {
    if (ImageCacheManager.instance === null) {
      ImageCacheManager.instance = new ImageCacheManager();
    }
    return ImageCacheManager.instance;
  }

  // 切换缓存策略
  switchPolicy(policy: CachePolicy): void {
    const success = this.cacheContext.setStrategy(policy);
    console.info(`[ImageCache] Policy switched to "${policy}": ${success}`);
  }

  // 获取当前策略
  getCurrentPolicy(): string {
    return this.cacheContext.getCurrentStrategyName();
  }

  // 加载图片(带缓存)
  async loadImage(url: string): Promise<string | null> {
    // 步骤一:检查缓存
    const cached = await this.cacheContext.get(url);
    if (cached !== null) {
      console.info(`[ImageCache] HIT: ${url} (${this.cacheContext.getCurrentStrategyName()})`);
      return cached;
    }

    // 步骤二:防止重复加载
    if (this.loadingUrls.has(url)) {
      return new Promise((resolve) => {
        const timer = setInterval(() => {
          if (!this.loadingUrls.has(url)) {
            clearInterval(timer);
            this.cacheContext.get(url).then(resolve);
          }
        }, 100);
      });
    }

    this.loadingUrls.add(url);
    console.info(`[ImageCache] MISS: ${url} --- fetching...`);

    try {
      // 步骤三:模拟网络请求(实际项目替换为 HTTP 请求)
      const imageData = await this.simulateNetworkFetch(url);

      // 步骤四:写入缓存
      await this.cacheContext.set(url, imageData);

      // 步骤五:通知监听器
      this.notifyListeners(url, imageData);

      return imageData;
    } catch (err) {
      console.error(`[ImageCache] Failed to load ${url}:`, err);
      return null;
    } finally {
      this.loadingUrls.delete(url);
    }
  }

  // 订阅图片加载完成事件
  subscribe(url: string, callback: LoadedCallback): () => void {
    const existing = this.listeners.get(url) || [];
    existing.push(callback);
    this.listeners.set(url, existing);

    return () => {
      const list = this.listeners.get(url) || [];
      const idx = list.indexOf(callback);
      if (idx !== -1) list.splice(idx, 1);
    };
  }

  private notifyListeners(url: string, imageData: string): void {
    const list = this.listeners.get(url) || [];
    list.forEach(cb => cb(url, imageData));
  }

  // 清除所有缓存
  async clearCache(): Promise<void> {
    await this.cacheContext.clear();
    console.info("[ImageCache] All caches cleared");
  }

  // 获取缓存统计
  async getCacheStats(): Promise<{ policy: string; size: number }> {
    return {
      policy: this.getCurrentPolicy(),
      size: await this.cacheContext.size()
    };
  }

  // 模拟网络请求
  private simulateNetworkFetch(url: string): Promise<string> {
    return new Promise((resolve) => {
      setTimeout(() => {
        // 返回模拟的 Base64 图片数据
        resolve(`data:image/png;base64,SIMULATED_IMAGE_FOR_${url.split("/").pop()}`);
      }, 300 + Math.random() * 500);
    });
  }
}

// 在 ArkUI @Component 中的使用示例
@Component
struct CachedImageComponent {
  @State imageSrc: string = "";
  @State loading: boolean = false;
  @State cacheStats: string = "";

  private imageUrl: string;
  private cacheManager: ImageCacheManager = ImageCacheManager.getInstance();

  // 当前缓存策略状态(供 UI 展示)
  @State currentPolicy: string = "HybridCache";

  @Builder
  constructor(imageUrl: string) {
    this.imageUrl = imageUrl;
    this.imageSrc = "";
    this.loading = true;
    this.cacheStats = "";
    this.currentPolicy = this.cacheManager.getCurrentPolicy();
  }

  aboutToAppear(): void {
    this.loadImage();
  }

  async loadImage(): Promise<void> {
    this.loading = true;
    const result = await this.cacheManager.loadImage(this.imageUrl);
    this.imageSrc = result || "";
    this.loading = false;

    const stats = await this.cacheManager.getCacheStats();
    this.currentPolicy = stats.policy;
    this.cacheStats = `策略: ${stats.policy} | 缓存数: ${stats.size}`;
  }

  build() {
    Column() {
      if (this.loading) {
        Progress({ type: ProgressType.Ring })
          .width(40)
          .color("#1890ff")
        Text("加载中...")
          .fontSize(12)
          .fontColor("#999")
          .margin({ top: 4 })
      } else if (this.imageSrc !== "") {
        Image(this.imageSrc)
          .width(200)
          .height(200)
          .objectFit(ImageFit.Cover)
          .borderRadius(12)
      } else {
        Text("加载失败")
          .fontSize(14)
          .fontColor("#999")
      }

      Text(this.cacheStats)
        .fontSize(10)
        .fontColor("#bbb")
        .margin({ top: 6 })
    }
    .onClick(() => {
      // 每次点击循环切换缓存策略
      const policies: CachePolicy[] = ["memory", "disk", "hybrid"];
      const idx = policies.indexOf(this.currentPolicy.toLowerCase() as CachePolicy);
      const next = policies[(idx + 1) % policies.length];
      this.cacheManager.switchPolicy(next);
      this.loadImage();
    })
  }
}

export { ImageCacheManager, CachedImageComponent };

5.4 小结

策略模式的核心优势在于运行时切换行为 ,而无需修改调用方代码。在图片缓存管理器中,CacheContext 对外暴露统一的 loadImage 接口,而具体使用哪种缓存策略完全由 switchPolicy 方法决定。这意味着在开发测试阶段可以使用"无缓存"策略方便调试,上线后切换为"混合缓存"策略提升性能,存储紧张时切换为"仅磁盘缓存"------无需改动任何业务代码。ArkUI 开发中,但凡遇到"同一操作有多种不同实现、需要运行时选择"的场景,都可以优先考虑策略模式。


六、模式融合:构建一个综合示例应用

6.1 各模式协同工作的全链路示例

在实际项目中,上述五种设计模式并非孤立存在。以下用一个简化版的"任务管理 App"来展示它们的协同工作方式:

typescript 复制代码
// 综合示例:TaskManagerApp.ets
// Builder 模式 + 单例模式 + 工厂模式 + 观察者模式 + 策略模式 协同作战

// ==================== 数据模型 & Builder ====================

class Task {
  id: string;
  title: string;
  description: string;
  priority: "low" | "medium" | "high";
  status: "pending" | "in_progress" | "completed";
  createdAt: Date;
  tags: string[];

  private constructor(builder: TaskBuilder) {
    this.id = builder.id;
    this.title = builder.title;
    this.description = builder.description;
    this.priority = builder.priority;
    this.status = builder.status;
    this.createdAt = builder.createdAt;
    this.tags = [...builder.tags];
  }

  static create(builder: TaskBuilder): Task {
    return new Task(builder);
  }
}

class TaskBuilder {
  id: string = "";
  title: string = "";
  description: string = "";
  priority: "low" | "medium" | "high" = "medium";
  status: "pending" | "in_progress" | "completed" = "pending";
  createdAt: Date = new Date();
  tags: string[] = [];

  setTitle(title: string): TaskBuilder { this.title = title; return this; }
  setDescription(desc: string): TaskBuilder { this.description = desc; return this; }
  setPriority(p: "low" | "medium" | "high"): TaskBuilder { this.priority = p; return this; }
  setStatus(s: "pending" | "in_progress" | "completed"): TaskBuilder { this.status = s; return this; }
  addTag(tag: string): TaskBuilder { this.tags.push(tag); return this; }
  build(): Task { return Task.create(this); }
}

// ==================== 任务仓库(单例 + 工厂) ====================

class TaskRepository {
  private static instance: TaskRepository | null = null;
  private tasks: Task[] = [];
  private cache: CacheContext<Task>;

  private constructor() {
    // 使用混合缓存策略
    const hybridCache = new HybridCacheStrategy<Task>();
    this.cache = new CacheContext<Task>(hybridCache);
    this.cache.registerStrategy("memory", new MemoryCacheStrategy<Task>(500));
    this.cache.registerStrategy("disk", new DiskCacheStrategy<Task>());
    this.cache.registerStrategy("hybrid", new HybridCacheStrategy<Task>());
  }

  static getInstance(): TaskRepository {
    if (TaskRepository.instance === null) {
      TaskRepository.instance = new TaskRepository();
    }
    return TaskRepository.instance;
  }

  // 工厂方法:添加任务
  addTask(builder: TaskBuilder): Task {
    const task = builder.build();
    this.tasks.push(task);
    this.cache.set(task.id, task).catch(() => {}); // 异步缓存
    EventHub.getInstance().emit("task:added", task);
    return task;
  }

  // 获取所有任务
  getAllTasks(): Task[] {
    return [...this.tasks];
  }

  // 切换缓存策略
  switchCachePolicy(policy: "memory" | "disk" | "hybrid"): void {
    this.cache.setStrategy(policy);
  }

  getCachePolicy(): string {
    return this.cache.getCurrentStrategyName();
  }
}

// ==================== 视图模型 ====================

class TaskListViewModel {
  @State tasks: Task[] = [];
  @State filter: string = "all";
  private repository: TaskRepository = TaskRepository.getInstance();
  private eventHub: EventHub = EventHub.getInstance();

  aboutToAppear(): void {
    this.eventHub.on("task:added", (task: Task) => {
      this.tasks = this.repository.getAllTasks();
    });

    // 预加载一些示例任务
    if (this.tasks.length === 0) {
      this.loadSampleData();
    }
  }

  aboutToDisappear(): void {
    this.eventHub.off("task:added", () => {});
  }

  private loadSampleData(): void {
    const repo = TaskRepository.getInstance();
    const builder = new TaskBuilder();

    repo.addTask(
      builder
        .setTitle("完成 ArkTS 设计模式文章")
        .setDescription("撰写 Builder、单例、工厂、观察者、策略五种模式")
        .setPriority("high")
        .addTag("写作")
        .addTag("HarmonyOS")
    );

    repo.addTask(
      new TaskBuilder()
        .setTitle("Review 分布式软总线代码")
        .setDescription("检查 DeviceManager 和 KVStore 的使用")
        .setPriority("medium")
        .addTag("代码审查")
    );

    repo.addTask(
      new TaskBuilder()
        .setTitle("整理 API 12 新特性笔记")
        .setDescription("汇总并发、元服务、动画、分布式四大特性")
        .setPriority("low")
        .setStatus("completed")
        .addTag("学习")
    );

    this.tasks = repo.getAllTasks();
  }

  getFilteredTasks(): Task[] {
    if (this.filter === "all") return this.tasks;
    if (this.filter === "pending") return this.tasks.filter(t => t.status === "pending");
    if (this.filter === "completed") return this.tasks.filter(t => t.status === "completed");
    return this.tasks;
  }
}

// ==================== ArkUI 页面 ====================

@Entry
@Component
struct TaskManagerPage {
  @State viewModel: TaskListViewModel = new TaskListViewModel();

  build() {
    Column() {
      Text("任务管理")
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .width("100%")
        .padding(16)

      // 筛选栏
      Row() {
        ForEach(["all", "pending", "completed"], (tab: string) => {
          Button(tab === "all" ? "全部" : tab === "pending" ? "待办" : "已完成")
            .fontSize(13)
            .height(32)
            .borderRadius(16)
            .onClick(() => {
              this.viewModel.filter = tab;
            })
        })
      }
      .width("100%")
      .padding({ left: 16, right: 16 })
      .justifyContent(FlexAlign.Start)
      .gap(8)

      // 任务列表
      List() {
        ForEach(this.viewModel.getFilteredTasks(), (task: Task) => {
          ListItem() {
            Column() {
              Row() {
                Text(task.title)
                  .fontSize(16)
                  .fontWeight(FontWeight.Medium)

                Blank()

                Text(task.status === "completed" ? "✓" : task.priority === "high" ? "🔥" : task.priority === "medium" ? "⚡" : "○")
                  .fontSize(16)
              }
              .width("100%")

              if (task.description !== "") {
                Text(task.description)
                  .fontSize(12)
                  .fontColor("#666")
                  .margin({ top: 4 })
                  .width("100%")
              }

              Row() {
                ForEach(task.tags, (tag: string) => {
                  Text(tag)
                    .fontSize(10)
                    .backgroundColor("#f0f0f0")
                    .borderRadius(4)
                    .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                    .margin({ right: 4 })
                })
              }
              .margin({ top: 6 })
              .alignItems(HorizontalAlign.Start)
            }
            .width("100%")
            .padding(12)
          }
        })
      }
      .layoutWeight(1)
      .width("100%")
      .divider({ strokeWidth: 0.5, color: "#eee" })
      .padding({ left: 16, right: 16 })
    }
    .width("100%")
    .height("100%")
    .backgroundColor("#fafafa")
  }
}

6.2 模式在示例中的协作关系

在这个综合示例中,五种设计模式各司其职:Builder 模式 负责 Task 对象的构造,使创建任务时的参数语义清晰、可组合;单例模式TaskRepository 承担全局唯一的数据仓库职责,避免多个组件操作各自独立的副本导致数据不一致;工厂方法TaskBuilder + addTask)将数据构造与存储逻辑解耦;观察者模式 通过 EventHub 实现任务添加后自动刷新列表,组件间无需直接引用;策略模式 通过 CacheContext 允许运行时切换缓存行为,不影响业务代码。

这种多模式协作的架构,正是大型 ArkUI 应用保持代码整洁的核心秘诀。


七、总结:设计模式在 ArkUI 中的使用指南

7.1 五种模式的核心价值回顾

设计模式 核心价值 最佳使用场景
Builder 链式构造,参数语义清晰 字段超过 5 个的可选参数对象
单例 全局唯一状态,避免重复初始化 配置中心、登录会话、缓存管理器
工厂 创建逻辑与使用逻辑解耦 多类型列表项、动态组件生成
观察者 一对多通知,组件解耦 跨页面状态同步、事件总线
策略 运行时切换行为,替换 if/else 缓存策略、加载策略、压缩算法

7.2 实践建议

优先考虑组合而非继承。 ArkTS 不支持传统类继承(class 不能继承多个父类),因此设计模式在 ArkUI 中应以组合(对象组合 + 接口实现)为主。例如,HybridCacheStrategy 组合了 MemoryCacheStrategyDiskCacheStrategy,而非让一个继承另一个。

保持策略接口精简。 CacheStrategy<T> 只定义了 5 个核心方法(set/get/remove/clear/size),足够覆盖绝大多数缓存场景。接口一旦膨胀,策略实现成本就会上升,维护成本随之提高。

观察者订阅要成对出现。 在 ArkUI 组件中,aboutToAppear() 中订阅的事件必须在 aboutToDisappear() 中取消订阅------这是防止内存泄漏的最基本纪律。可以使用 once() 订阅一次性事件来减少手动取消的工作量。

Builder 适合数据对象,不适合 UI 组件。 @Builder 装饰器用于 ArkUI 组件的声明式构建,而经典 Builder 模式更适合数据模型(如 TaskUserProfile)。两者可以结合使用,但职责不同,不要混淆。

不要为了模式而模式。 如果一个对象的构造只有两三个必选参数,直接用构造函数就好。设计模式是工具,不是装饰------当代码中真正出现"构造复杂"、"行为需要切换"、"组件需要解耦"的需求时,再引入对应模式。


相关推荐
解局易否结局6 小时前
鸿蒙原生开发实战|Native JSON 编解码与高性能数据序列化
华为·json·harmonyos
Helen_cai6 小时前
HarmonyOS ArkTS 实战:实现一个校园门禁与访客预约应用
深度学习·华为·harmonyos
不羁的木木7 小时前
HarmonyOS技术精讲-Connectivity Kit:蓝牙进阶——GATT服务与音频通道
华为·音视频·harmonyos
Helen_cai7 小时前
HarmonyOS ArkTS 实战:实现一个校园快递代取与跑腿应用
华为·harmonyos
happyness447 小时前
AI编程与设计模式
设计模式·ai编程
2301_768103497 小时前
HarmonyOS趣味相机实战第13篇:CameraPicker与CameraKit选型、系统拍照回退及架构边界
harmonyos·arkts·架构设计·camerapicker·camerakit
就是小王同学啊7 小时前
Spring小技巧之设计模式
sql·spring·设计模式
2301_768103498 小时前
HarmonyOS趣味相机实战第14篇:装饰素材强类型建模、锚点语义与套装组合校验
harmonyos·arkts·数据建模·arkui·相机贴纸
不羁的木木8 小时前
HarmonyOS技术精讲-Connectivity Kit:Wi-Fi进阶能力——热点、P2P与Aware
asp.net·harmonyos·p2p