系列文章《鸿蒙开发从入门到专家》第 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,不影响其他节点。
七、什么模式都别硬套
以上模式在特定场景下有实际价值。但最差的代码不是没有设计模式的代码,而是硬套设计模式的代码。
几个判断依据:
先有三处重复,再抽模式。 一处重复不需要模式,直接写最清晰。
模式解决的是变化问题,不是复杂度问题。 如果某个地方不会有变化,不需要用策略模式预留扩展点。
团队理解成本优先。 如果团队不熟悉责任链,一段简单的 if-else 比责任链更易维护。
ArkTS 的语言特性已经内建了部分模式。
@State是观察者,@Extend是装饰器,@Builder是模板方法。不要在语言特性之外再套一层。
小结
第 50 篇是"基础补强"阶段的最后一篇。前 50 篇覆盖了从零基础到有经验鸿蒙开发者需要的核心知识:
后 50 篇将进入"高级实战"和"专家深潜"阶段,针对真实业务场景做深度拆解。下篇开始第一个主题:大型应用的启动框架优化。