Administrator
发布于 2026-09-02 / 10,019 阅读
0
0

设计模式在鸿蒙里的应用:不是理论课,是代码级实践

系列文章《鸿蒙开发从入门到专家》第 50 篇 · 高级

设计模式是那种"听过了就觉得自己会了"的东西。但在实际鸿蒙项目里,哪些模式有用、哪些是过度设计、具体怎么落地,多数人没仔细想过。

这篇不讲 23 种 GoF 模式的定义,那些 wiki 上都有。讲的是在鸿蒙的 ArkTS + ArkUI 技术栈下,哪些设计模式真正解决了问题,附带可直接参考的代码。

一、观察者模式:ArkUI 的内置机制

ArkUI 的声明式范式本身就是观察者模式的实现。@State、@Prop、@Link、@Watch 这些装饰器做的就是"状态变化时通知依赖的 UI 组件刷新"。

但 ArkUI 的内置观察者粒度是组件级的。如果你的业务逻辑也需要观察者模式(比如多个非 UI 模块监听同一状态),需要自己实现。

场景:多模块监听登录状态变化

// pattern/observer/LoginStatusManager.ts

type Listener = (isLoggedIn: boolean) => void;

export class LoginStatusManager {
  private static instance: LoginStatusManager;
  private listeners: Set<Listener> = new Set();
  private _isLoggedIn: boolean = false;

  private constructor() {}

  static getInstance(): LoginStatusManager {
    if (!LoginStatusManager.instance) {
      LoginStatusManager.instance = new LoginStatusManager();
    }
    return LoginStatusManager.instance;
  }

  get isLoggedIn(): boolean {
    return this._isLoggedIn;
  }

  setLoggedIn(value: boolean): void {
    if (this._isLoggedIn === value) return;
    this._isLoggedIn = value;
    this.listeners.forEach(listener => listener(value));
  }

  subscribe(listener: Listener): void {
    this.listeners.add(listener);
  }

  unsubscribe(listener: Listener): void {
    this.listeners.delete(listener);
  }
}

使用:

// 网络模块:登录状态变化时更新请求头
class NetworkModule {
  constructor() {
    LoginStatusManager.getInstance().subscribe((loggedIn) => {
      if (loggedIn) {
        this.addAuthHeader();
      } else {
        this.removeAuthHeader();
      }
    });
  }
}

// 推送模块:登录状态变化时注册/注销推送
class PushModule {
  constructor() {
    LoginStatusManager.getInstance().subscribe((loggedIn) => {
      if (loggedIn) {
        this.registerPush();
      } else {
        this.unregisterPush();
      }
    });
  }
}

和 ArkUI 的 @Watch 不同,这个观察者不依赖组件生命周期。模块级的监听用自定义观察者,组件级的监听用 @Watch。

二、策略模式:替代冗长的 if-else

场景:不同登录方式的处理逻辑

一个登录页面支持账号密码、手机验证码、第三方账号三种方式。不用策略模式的话:

// 差:一坨 if-else
async function login(type: string, params: any) {
  if (type === 'password') {
    const result = await api.loginByPassword(params.username, params.password);
    // 处理密码登录
  } else if (type === 'sms') {
    if (!params.code || params.code.length !== 6) {
      throw new Error('验证码格式错误');
    }
    const result = await api.loginBySms(params.phone, params.code);
    // 处理短信登录
  } else if (type === 'thirdparty') {
    const result = await api.loginByThirdParty(params.provider, params.token);
    // 处理第三方登录
  }
}

每加一种登录方式就要改这个函数,违反开闭原则。用策略模式:

// pattern/strategy/LoginStrategy.ts

interface LoginStrategy {
  validate(params: any): boolean;
  execute(params: any): Promise<LoginResult>;
  getErrorType(error: any): string;
}

class PasswordLoginStrategy implements LoginStrategy {
  validate(params: any): boolean {
    return !!params.username && !!params.password;
  }

  async execute(params: any): Promise<LoginResult> {
    return await api.loginByPassword(params.username, params.password);
  }

  getErrorType(error: any): string {
    if (error.code === 401) return 'WRONG_CREDENTIALS';
    return 'UNKNOWN';
  }
}

class SmsLoginStrategy implements LoginStrategy {
  validate(params: any): boolean {
    if (!params.phone || params.phone.length !== 11) return false;
    if (!params.code || params.code.length !== 6) return false;
    return true;
  }

  async execute(params: any): Promise<LoginResult> {
    return await api.loginBySms(params.phone, params.code);
  }

  getErrorType(error: any): string {
    if (error.code === 429) return 'RATE_LIMITED';
    if (error.code === 400) return 'INVALID_CODE';
    return 'UNKNOWN';
  }
}

// 登录管理器
class LoginManager {
  private strategies: Map<string, LoginStrategy> = new Map();

  registerStrategy(type: string, strategy: LoginStrategy): void {
    this.strategies.set(type, strategy);
  }

  async login(type: string, params: any): Promise<LoginResult> {
    const strategy = this.strategies.get(type);
    if (!strategy) throw new Error(`Unknown login type: ${type}`);

    if (!strategy.validate(params)) {
      throw new Error('参数校验失败');
    }

    return await strategy.execute(params);
  }
}

新增登录方式只需要新增一个 Strategy 类 + 注册一行,不改已有代码。

三、工厂模式:创建复杂对象

场景:根据配置创建不同类型的卡片

鸿蒙的元服务支持多种卡片类型,每种卡片的创建逻辑不同:

// pattern/factory/CardFactory.ts

interface WidgetCard {
  render(): void;
  update(data: any): void;
}

class WeatherCard implements WidgetCard {
  render() { /* 渲染天气卡片 */ }
  update(data: any) { /* 更新天气数据 */ }
}

class CalendarCard implements WidgetCard {
  render() { /* 渲染日历卡片 */ }
  update(data: any) { /* 更新日程数据 */ }
}

class MusicCard implements WidgetCard {
  render() { /* 渲染音乐卡片 */ }
  update(data: any) { /* 更新播放状态 */ }
}

class CardFactory {
  static create(type: string): WidgetCard {
    switch (type) {
      case 'weather': return new WeatherCard();
      case 'calendar': return new CalendarCard();
      case 'music': return new MusicCard();
      default: throw new Error(`Unknown card type: ${type}`);
    }
  }
}

看起来和策略模式很像。区别在于:策略模式替换的是"行为",工厂模式创建的是"对象"。策略模式关注"怎么做",工厂模式关注"建什么"。

四、单例模式:用对场景

单例模式在鸿蒙里用得很多,但也用错得最多。单例适合管理全局唯一资源,不适合做工具类。

适合单例的场景

  • 全局状态管理(登录状态、主题模式)

  • 系统级资源的封装(蓝牙管理、传感器管理)

  • 基础设施服务(日志、网络客户端)

不适合单例的场景

  • 工具类(如字符串格式化、日期计算)→ 用静态方法

  • 数据模型对象(如用户信息、订单数据)→ 用普通类

  • 有状态的临时对象 → 每次创建新实例

ArkTS 中的单例实现

export class BluetoothManager {
  private static instance: BluetoothManager;
  private connectedDevices: Set<string> = new Set();

  private constructor() {
    // 初始化蓝牙
  }

  static getInstance(): BluetoothManager {
    if (!BluetoothManager.instance) {
      BluetoothManager.instance = new BluetoothManager();
    }
    return BluetoothManager.instance;
  }

  async connect(deviceId: string): Promise<void> {
    if (this.connectedDevices.has(deviceId)) return;
    // 连接逻辑
    this.connectedDevices.add(deviceId);
  }

  async disconnect(deviceId: string): Promise<void> {
    this.connectedDevices.delete(deviceId);
    // 断开逻辑
  }
}

注意:单例的生命周期和应用一样长。如果单例持有大量数据引用(如缓存的 Bitmap),会导致内存泄漏。单例里只放"管理器自身状态",不放"被管理的数据"。

五、装饰器模式:ArkTS 的天然优势

ArkTS 的装饰器(@Component、@Entry、@Builder、@Extend)是语言层面的支持。@Extend 就是一个典型的装饰器模式应用——在不修改原组件的情况下扩展其样式。

场景:给不同业务场景的按钮套用不同样式

// 基础按钮组件
@Component
struct BaseButton {
  @Prop label: string;
  build() {
    Button(this.label)
      .fontSize(16)
      .height(44)
  }
}

// 营销场景:扩展红色大按钮样式
@Extend(Button)
function marketingStyle() {
  .backgroundColor('#FF4500')
  .fontColor('#FFFFFF')
  .fontSize(20)
  .height(56)
  .borderRadius(28)
}

// 使用
@Entry
@Component
struct MarketingPage {
  build() {
    Column() {
      BaseButton({ label: '立即抢购' })
        .marketingStyle()
    }
  }
}

Builder 装饰器:组合 UI 片段

@Builder
function LoadingOverlay(content: string) {
  Column() {
    LoadingProgress().width(40).height(40)
    Text(content).fontSize(14).fontColor('#666')
  }
  .justifyContent(FlexAlign.Center)
  .width('100%')
  .height('100%')
  .backgroundColor('#80000000')
}

// 任何组件都可以复用这个 Builder
@Entry
@Component
struct ContentPage {
  @State isLoading: boolean = false;

  build() {
    Stack() {
      // 页面内容
      Column() { Text('Content') }

      if (this.isLoading) {
        LoadingOverlay('加载中...')
      }
    }
  }
}

六、责任链模式:请求处理管线

场景:网络请求的预处理管线

一个网络请求发出前要经过:参数校验 → 权限检查 → 日志记录 → Token 注入 → 实际发送。如果用 if-else 链,代码会很长且顺序固定。

// pattern/chain/RequestHandler.ts

interface RequestHandler {
  setNext(handler: RequestHandler): RequestHandler;
  handle(request: RequestConfig): Promise<RequestConfig>;
}

abstract class AbstractHandler implements RequestHandler {
  private nextHandler: RequestHandler | null = null;

  setNext(handler: RequestHandler): RequestHandler {
    this.nextHandler = handler;
    return handler;
  }

  async handle(request: RequestConfig): Promise<RequestConfig> {
    if (this.nextHandler) {
      return this.nextHandler.handle(request);
    }
    return request;
  }
}

class ValidationHandler extends AbstractHandler {
  async handle(request: RequestConfig): Promise<RequestConfig> {
    if (!request.url || !request.method) {
      throw new Error('Missing required fields');
    }
    return super.handle(request);
  }
}

class AuthHandler extends AbstractHandler {
  async handle(request: RequestConfig): Promise<RequestConfig> {
    const token = AppStorage.get<string>('token');
    if (token) {
      request.headers = { ...request.headers, 'Authorization': `Bearer ${token}` };
    }
    return super.handle(request);
  }
}

class LogHandler extends AbstractHandler {
  async handle(request: RequestConfig): Promise<RequestConfig> {
    logger.info(`Request: ${request.method} ${request.url}`);
    const result = await super.handle(request);
    logger.info(`Request processed: ${request.url}`);
    return result;
  }
}

// 组装责任链
const chain = new ValidationHandler();
chain.setNext(new AuthHandler()).setNext(new LogHandler());

// 使用
await chain.handle(requestConfig);

责任链的好处是顺序可调、节点可插拔。需要加缓存检查就在链中间插入一个 CacheHandler,不影响其他节点。

七、什么模式都别硬套

以上模式在特定场景下有实际价值。但最差的代码不是没有设计模式的代码,而是硬套设计模式的代码。

几个判断依据:

  1. 先有三处重复,再抽模式。 一处重复不需要模式,直接写最清晰。

  2. 模式解决的是变化问题,不是复杂度问题。 如果某个地方不会有变化,不需要用策略模式预留扩展点。

  3. 团队理解成本优先。 如果团队不熟悉责任链,一段简单的 if-else 比责任链更易维护。

  4. ArkTS 的语言特性已经内建了部分模式。 @State 是观察者,@Extend 是装饰器,@Builder 是模板方法。不要在语言特性之外再套一层。

小结

第 50 篇是"基础补强"阶段的最后一篇。前 50 篇覆盖了从零基础到有经验鸿蒙开发者需要的核心知识:

阶段

篇号

覆盖内容

入门

01-10

DevEco、ArkTS、ArkUI、路由、数据、网络、组件、性能、分布式、架构

元能力

11-20

元服务、安全、推送、音视频、地图、支付、AI、跨平台、测试、发布

专项能力

21-30

Canvas、并发、RDB、文件、蓝牙、传感器、车机、手表、IoT、i18n

工程化

31-40

性能剖析、包体积、安全加固、组件化、动态化、CI/CD、监控、架构

基础补强

41-50

ArkWeb、Stage模型、NAPI、UX规范、单元测试、UI测试、调试、Framework、H5容器、设计模式

后 50 篇将进入"高级实战"和"专家深潜"阶段,针对真实业务场景做深度拆解。下篇开始第一个主题:大型应用的启动框架优化。


评论