1. 为什么需要类的继承与多态
在鸿蒙应用开发中,我们经常遇到需要复用已有代码的场景。比如开发一个电商应用时,商品详情页、购物车、订单页面都需要显示商品信息。如果每个页面都重新定义商品数据结构,不仅效率低下,而且维护困难。
类的继承就像生物学中的遗传机制。想象一下,如果每次生育后代都要从头设计DNA,那将是多么低效。在编程中,父类(基类)就是那个包含通用属性和方法的"基因库",子类(派生类)则继承这些特性并可以添加自己的特殊功能。
ArkTS作为TypeScript的超集,其继承机制与ES6+标准基本一致。多态则让不同类型的对象可以对同一方法调用做出不同响应,就像不同乐器对"演奏"指令会有不同表现。
2. ArkTS中的类继承实现
2.1 基础继承语法
在ArkTS中,使用extends关键字实现继承。让我们通过一个实际案例来理解:
typescript复制// 基类:电子产品
class ElectronicProduct {
name: string;
price: number;
constructor(name: string, price: number) {
this.name = name;
this.price = price;
}
displayInfo(): void {
console.log(`产品名称:${this.name},价格:${this.price}元`);
}
}
// 派生类:智能手机
class SmartPhone extends ElectronicProduct {
osType: string; // 新增属性
constructor(name: string, price: number, osType: string) {
super(name, price); // 必须首先调用父类构造函数
this.osType = osType;
}
// 重写父类方法
displayInfo(): void {
console.log(`[智能手机] ${this.name},系统:${this.osType},价格:${this.price}元`);
}
}
// 使用示例
const myPhone = new SmartPhone('华为P50', 4999, 'HarmonyOS');
myPhone.displayInfo();
关键点说明:
super()调用必须放在子类构造函数的最前面- 子类可以添加新属性和方法
- 方法重写不是必须的,按需实现
2.2 继承中的访问控制
ArkTS支持三种访问修饰符:
public(默认):任何地方都可访问protected:仅类内部和子类可访问private:仅类内部可访问
typescript复制class Base {
public publicField = 1;
protected protectedField = 2;
private privateField = 3;
}
class Derived extends Base {
accessTest() {
console.log(this.publicField); // √ 可访问
console.log(this.protectedField); // √ 可访问
console.log(this.privateField); // × 编译错误
}
}
提示:良好的封装习惯是尽量使用
private,只暴露必要的接口。这可以减少类之间的耦合,提高代码可维护性。
3. 多态的实现与应用
3.1 多态的基本概念
多态(Polymorphism)允许不同类的对象对同一消息做出不同响应。在鸿蒙UI开发中,多态尤其重要,比如不同组件对触摸事件的响应可能完全不同。
typescript复制// 抽象基类:支付方式
abstract class PaymentMethod {
abstract pay(amount: number): void;
}
// 具体实现类
class Alipay extends PaymentMethod {
pay(amount: number) {
console.log(`支付宝支付:${amount}元`);
}
}
class WeChatPay extends PaymentMethod {
pay(amount: number) {
console.log(`微信支付:${amount}元`);
}
}
// 使用多态
function processPayment(method: PaymentMethod, amount: number) {
method.pay(amount);
}
const ali = new Alipay();
const wechat = new WeChatPay();
processPayment(ali, 100); // 输出:支付宝支付:100元
processPayment(wechat, 200); // 输出:微信支付:200元
3.2 接口与抽象类
ArkTS支持接口和抽象类来实现多态:
typescript复制// 接口定义
interface Drawable {
draw(): void;
}
// 抽象类
abstract class Shape implements Drawable {
abstract area(): number;
draw() {
console.log('绘制形状...');
}
}
class Circle extends Shape {
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
area(): number {
return Math.PI * this.radius ** 2;
}
draw() {
console.log(`绘制圆形,半径:${this.radius}`);
}
}
接口与抽象类的区别:
- 接口:纯抽象,不能包含实现
- 抽象类:可以包含部分实现
- 类可以实现多个接口,但只能继承一个类
4. 实战:鸿蒙UI组件继承案例
4.1 自定义基础组件
在鸿蒙应用开发中,我们经常需要创建自定义组件。下面是一个基础按钮组件的实现:
typescript复制@Component
export struct BaseButton {
@State label: string = '按钮';
@Prop onClick: () => void;
build() {
Button(this.label)
.width(100)
.height(40)
.onClick(() => {
this.onClick();
});
}
}
4.2 扩展特殊按钮
现在我们需要一个带图标的按钮,可以通过继承实现:
typescript复制@Component
export struct IconButton extends BaseButton {
@Prop icon: Resource;
build() {
Row() {
Image(this.icon)
.width(20)
.height(20)
Text(this.label)
}
.width(120)
.height(40)
.onClick(() => {
this.onClick();
});
}
}
4.3 使用多态管理组件
在页面中使用这些组件时,可以利用多态特性:
typescript复制@Entry
@Component
struct MyPage {
private buttons: BaseButton[] = [
new BaseButton({ label: '普通按钮' }),
new IconButton({ label: '收藏', icon: $r('app.media.ic_favorite') })
];
build() {
Column() {
ForEach(this.buttons, (button: BaseButton) => {
button.build()
})
}
}
}
5. 常见问题与最佳实践
5.1 继承的误用与滥用
虽然继承很强大,但滥用会导致代码难以维护。以下是几个常见陷阱:
-
过深的继承层次:超过3层的继承关系通常意味着设计有问题
- 不好:A → B → C → D → E
- 更好:使用组合代替继承
-
脆弱的基类问题:修改父类可能意外破坏子类
- 解决方案:优先使用组合,或确保父类稳定
-
不合理的继承关系:比如让"汽车"继承"发动机"
- 正确关系应该是"汽车"包含"发动机"
5.2 多态性能考量
在鸿蒙应用开发中,过度使用多态可能影响性能:
-
虚函数表开销:动态绑定需要额外的查找过程
- 对性能敏感的部分考虑使用final方法
-
内存占用:每个对象需要存储类型信息
- 在大量实例的场景下需要注意
-
优化建议:
typescript复制// 标记为final可以避免虚函数查找 class Optimized { final doSomething() { // 实现 } }
5.3 ArkTS特有注意事项
-
组件继承限制:
- 自定义组件只能继承自框架提供的组件或另一个自定义组件
- 不能多重继承
-
状态管理:
- 子组件不能直接修改父组件的@State变量
- 需要通过@Prop或@Link建立双向绑定
-
生命周期:
- 子组件的aboutToAppear会在父组件之后调用
- 子组件的aboutToDisappear会在父组件之前调用
在实际项目中,我通常会创建一个BasePage组件来处理通用逻辑(如权限检查、网络状态监听),然后让各个页面继承它。这样可以避免重复代码,同时通过方法重写实现特定页面的特殊行为。
