零基础鸿蒙应用开发第二十三节:抽象类的场景应用

零基础鸿蒙应用开发学习计划表

【学习目标】

  1. 理解抽象类的核心设计意义,掌握ArkTS中abstract关键字的使用规则,解决普通父类可实例化无业务意义对象的问题;
  2. 掌握抽象方法的声明与实现规则,强制子类差异化实现核心业务逻辑(如折后价计算),解决普通父类方法默认实现的局限性;
  3. 掌握抽象类+多态的进阶应用,实现抽象类引用管理所有商品对象;
  4. 从语法、业务、工程化三个维度,吃透抽象类与普通类的核心差异,能在实际开发中正确选型。

【学习重点】

  • 抽象类的核心特性:不能直接实例化,可包含普通属性/方法+抽象方法,是“半抽象半具体”的基类;
  • 抽象方法的强制实现规则:抽象类中的抽象方法无方法体,子类必须用override实现(显式重写),否则编译报错;
  • 鸿蒙电商场景下的实战改造:将Goods普通类改为AbstractGoods抽象类,子类(数码、图书)实现抽象的折后价计算方法;
  • 抽象类+多态:通过抽象类引用统一管理商品列表;
  • 抽象类与普通类的差异对比:重点关注实例化限制、方法强制实现、业务约束性三个核心维度。

一、工程结构

基于上一节ClassObjectDemo_1复制一份重命名为ClassObjectDemo_2,核心修改仅将父类改为抽象类:

ClassObjectDemo_2/
├── entry/                          # 应用主模块
│   ├── src/
│   │   ├── main/
│   │   │   ├── ets/                # ArkTS代码根目录
│   │   │   │   ├── pages/          # 页面代码目录
│   │   │   │   │   └── Index.ets   # 测试页面(新增UI渲染逻辑)
│   │   │   │   └── model/          # 业务模型类目录
│   │   │   │       ├── AbstractGoods.ets  # 父类:抽象商品基类(替换原Goods.ets)
│   │   │   │       ├── DigitalGoods.ets   # 子类:数码商品类(修改实现抽象方法)
│   │   │   │       └── BookGoods.ets      # 子类:图书商品类(修改实现抽象方法)
│   │   │   ├── resources/          # 资源目录(非本节重点)
│   │   │   └── module.json5        # 模块配置文件
│   │   └── oh-package.json5        # 工程依赖配置
└── hvigorfile.ts                   # 构建脚本(默认生成)

二、抽象类的核心意义:解决普通父类的局限性

上一节我们发现普通父类Goods存在两大核心问题:

  1. 业务逻辑缺陷:可直接实例化new Goods(...)创建无业务意义的“纯商品”对象,违背电商中“商品必须是具体类型(数码、图书)”的业务规则;
  2. 技术约束缺陷:父类calculateFinalPrice方法是固定实现(售价×折扣),无法强制子类根据业务场景差异化实现(如数码叠加会员折扣、图书叠加满减折扣),易导致业务逻辑不统一。

抽象类的核心价值

  • 语法层面禁止抽象类的直接实例化,解决“无意义对象”问题;
  • 约束层面通过抽象方法强制子类实现核心业务逻辑,解决“逻辑统一化”问题;
  • 保留普通类的代码复用特性(可包含普通属性、普通方法),是普通类与接口之间的“中间层”。

三、抽象类与抽象方法的语法规则

ArkTS中通过abstract关键字定义抽象类和抽象方法,核心语法与规则如下:

3.1 改造 Goods 类为抽象类

文件重命名:

  1. 右击原Goods.ets文件名→选择Rename重命名为AbstractGoods.ets
  2. 类名Goods修改为AbstractGoods,并添加abstract关键字;
  3. calculateFinalPrice改为抽象方法,保留原有普通属性和方法(如printBaseInfo、getter/setter)。
// model/AbstractGoods.ets
export abstract class AbstractGoods {
  // 公共属性
  public name: string;
  public category: string = "商品分类";

  // 保护属性:子类可访问,类外不可访问
  protected costPrice: number;

  // 私有属性:仅当前类可访问
  private _price: number;
  private _stock: number;
  private _discount: number = 1;

  constructor(
    name: string,
    price: number,
    stock: number,
    costPrice: number,
    discount?: number,
    category?: string
  ) {
    this.name = name.trim() || "未命名商品";
    this._price = Math.max(price, 0);
    this._stock = Math.max(stock, 0);
    this.costPrice = Math.max(costPrice, 0);

    // 直接赋值私有属性,避免setter依赖未完全初始化的实例
    if (discount && discount > 0 && discount <= 1) {
      this._discount = discount;
    }

    this.category = category?.trim() || this.category;
  }

  // 售价的getter/setter
  set price(newPrice: number) {
    if (newPrice < 0) {
      console.warn(`【${this.name}】售价不能为负,修改失败`);
      return;
    }
    this._price = newPrice;
    console.log(`【${this.name}】售价修改为:${newPrice}元`);
  }

  get price() {
    return this._price;
  }

  // 折扣的getter/setter
  set discount(newDiscount: number) {
    if (newDiscount > 0 && newDiscount <= 1) {
      this._discount = newDiscount;
      console.log(`【${this.name}】折扣调整为:${newDiscount * 10}折`);
    } else {
      console.warn(`【${this.name}】折扣无效,需0~1之间,修改失败`);
    }
  }

  get discount() {
    return this._discount;
  }

  // 库存的getter/setter
  set stock(newStock: number) {
    if (newStock < 0) {
      console.warn(`【${this.name}】库存不能为负,修改失败`);
      return;
    }
    this._stock = newStock;
    console.log(`【${this.name}】库存修改为:${newStock}件`);
  }

  get stock() {
    return this._stock;
  }

  // 普通方法:打印商品基础信息
  printBaseInfo(): void {
    // 调用抽象方法(子类已强制实现,无运行时错误)
    const finalPrice = this.calculateFinalPrice();
    const showDiscount = this.discount * 10;

    console.log(`
      商品名称:${this.name}
      商品分类:${this.category}
      商品原价:${this.price}元
      商品折扣:${showDiscount}折
      折后售价:${finalPrice}元
      商品库存:${this.stock}件
    `);
  }

  // 抽象方法:强制子类实现差异化折后价计算
  public abstract calculateFinalPrice(): number;
}

3.2 抽象类核心规则

  1. 抽象类不能直接实例化new AbstractParent("测试", 100)会直接编译报错,只能作为父类被继承;
  2. 抽象方法必须在抽象类中:普通类中不能定义抽象方法,否则编译报错;
  3. 子类必须实现所有抽象方法:若子类未实现抽象类的任意一个抽象方法,子类也必须声明为抽象类(示例:abstract class UnfinishedGoods extends AbstractParent {});
  4. 抽象类可包含普通成员:抽象类不是“全抽象”的,可包含普通属性、普通方法、构造函数,用于代码复用;
  5. 抽象方法无方法体:抽象方法仅定义方法签名(方法名、参数、返回值),具体实现由子类完成。

3.3 修改子类实现抽象方法

DigitalGoods数码商品类继承抽象类

新增会员折扣逻辑(折后价=售价×折扣×0.95),强制实现抽象方法calculateFinalPrice,并优化空值处理:

// model/DigitalGoods.ets
import { AbstractGoods } from './AbstractGoods';

export class DigitalGoods extends AbstractGoods {
  // 专属属性:品牌、保修年限(私有变量+getter/setter)
  private _brand: string;
  private _warranty: number;

  /**
   * 构造函数:初始化父类属性+专属属性
   */
  constructor(
    name: string,
    price: number,
    stock: number,
    costPrice: number,
    brand: string,
    warranty: number,
    discount?: number,
    category?: string
  ) {
    // 补充默认分类,贴合电商业务逻辑
    super(name, price, stock, costPrice, discount, category || "数码产品");
    // 空值保护:避免brand为undefined时trim报错
    this._brand = (brand || "").trim() || "未知品牌";
    this._warranty = Math.max(warranty, 1); // 保修年限至少1年
  }

  // 品牌的getter/setter
  set brand(newBrand: string) {
    this._brand = (newBrand || "").trim() || "未知品牌";
  }

  get brand(): string {
    return this._brand;
  }

  set warranty(newWarranty: number) {
    if (newWarranty < 1) {
       console.warn(`【${this.name}】保修期至少1年,修改失败`);
    }
    this._warranty = Math.max(newWarranty, 1);
  }

  get warranty(): number {
    return this._warranty;
  }

  // 专属方法:计算单件利润
  calculateProfit(): number {
    const finalPrice = this.calculateFinalPrice();
    return parseFloat((finalPrice - this.costPrice).toFixed(2));
  }

  // 重写普通方法:扩展商品信息打印
  public override printBaseInfo(): void {
    super.printBaseInfo(); // 复用父类逻辑
    console.log(`
      品牌:${this.brand}
      保修年限:${this.warranty}年
      单件利润:${this.calculateProfit()}元
    `);
  }

  // 必须实现抽象方法:数码商品叠加会员折扣(额外95折)
  public override calculateFinalPrice(): number {
    const baseFinalPrice = this.price * this.discount;
    const memberDiscountPrice = baseFinalPrice * 0.95; // 会员额外95折
    return parseFloat(memberDiscountPrice.toFixed(2));
  }
}

BookGoods图书商品类

新增满减逻辑(折后价≥50减10),规范私有属性+getter/setter:

// model/BookGoods.ets
import { AbstractGoods } from './AbstractGoods';

export class BookGoods extends AbstractGoods {
  // 专属属性:作者、出版日期(私有变量+getter/setter)
  private _author: string;
  private _publishDate: string;

  constructor(
    name: string,
    price: number,
    stock: number,
    costPrice: number,
    author: string,
    publishDate: string,
    discount?: number,
    category?: string
  ) {
    // 补充默认分类,贴合电商业务逻辑
    super(name, price, stock, costPrice, discount, category || "图书");
    this._author = author.trim() || "未知作者";
    this._publishDate = publishDate.trim() || "未知日期";
  }

  // 作者的getter/setter
  set author(newAuthor: string) {
    this._author = (newAuthor || "").trim() || "未知作者";
  }

  get author(): string {
    return this._author;
  }

  // 出版日期的getter/setter
  set publishDate(newDate: string) {
    this._publishDate = (newDate || "").trim() || "未知日期";
  }

  get publishDate(): string {
    return this._publishDate;
  }

  // 专属方法:计算图书利润
  calculateBookProfit(): number {
    const finalPrice = this.calculateFinalPrice();
    return parseFloat((finalPrice - this.costPrice).toFixed(2));
  }

  // 重写普通方法:扩展商品信息打印
  public override printBaseInfo(): void {
    super.printBaseInfo();
    console.log(`
      作者:${this._author}
      出版日期:${this._publishDate}
      单件利润:${this.calculateBookProfit()}元
    `);
  }

  // 必须实现抽象方法:图书商品满50减10
  public override calculateFinalPrice(): number {
    let finalPrice = this.price * this.discount;
    // 满50减10
    if (finalPrice >= 50) {
      finalPrice -= 10;
    }
    return parseFloat(finalPrice.toFixed(2));
  }

  // 专属方法:获取作者信息
  getAuthorInfo(): string {
    return `《${this.name}》的作者是${this._author}`;
  }
}

四、主页面:抽象类+多态

Index.ets中,使用AbstractGoods抽象类引用统一管理所有子类对象,结合鸿蒙ArkUI实现极简商品列表渲染(UI仅为示例,核心聚焦抽象类+多态逻辑):

// pages/Index.ets
import { AbstractGoods } from '../model/AbstractGoods';
import { DigitalGoods } from '../model/DigitalGoods';
import { BookGoods } from '../model/BookGoods';

@Entry
@Component
struct Index {
  // 商品列表:抽象类引用(多态核心)
  @State goodsList: AbstractGoods[] = [];
  // 页面加载时初始化商品数据
  aboutToAppear(): void {
    // 初始化商品数据(抽象类无法直接实例化,仅能创建子类对象)
    const digital = new DigitalGoods("鸿蒙Mate70 Pro 手机", 5999, 100, 4000, "华为", 2, 0.8, "数码产品");
    const book = new BookGoods("鸿蒙开发基础实战", 69, 500, 30, "散修", "2025-12-23", 0.7, "图书");

    // 报错:Cannot create an instance of an abstract class. <ArkTSCheck>
    // 抽象类无法直接实例化,仅能创建子类对象
    // const abstrct:AbstractGoods = new AbstractGoods("鸿蒙开发基础实战", 69, 500, 30, "图书");

    // 抽象类引用管理子类对象
    this.goodsList = [digital, book];

    // 控制台打印商品信息(验证抽象方法实现)
    this.goodsList.forEach((goods, index) => {
      console.log(`\n===== 商品${index + 1}信息 =====`);
      goods.printBaseInfo();
    });
  }

  // 批量更新库存方法
  private batchUpdateStock(num: number): void {
    this.goodsList.forEach(goods => {
      goods.stock = goods.stock + num;
    });
    // 刷新UI(基础阶段简易实现)
    const  goodsList = this.goodsList
    this.goodsList = [...goodsList];
  }

  build() {
    Column() {
      // 页面标题
      Text("鸿蒙电商商品管理-抽象类与多态")
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 20 });

      // 批量更新库存按钮
      Button("批量增加50件库存")
        .width(200)
        .height(40)
        .margin({ bottom: 20 })
        .onClick(() => {
          this.batchUpdateStock(50);
        });

      // 商品列表渲染(仅作示例演示)
      List({ space: 10 }) {
        ForEach(
          this.goodsList,
          (goods: AbstractGoods) => {
            ListItem() {
              Column({ space: 8 }) {
                // 通用信息
                Text(`商品名称:${goods.name}`)
                  .fontSize(18)
                  .fontWeight(FontWeight.Medium);
                Text(`分类:${goods.category} | 原价:${goods.price}元 | 折扣:${goods.discount * 10}折`)
                  .fontSize(14);
                Text(`折后价:${goods.calculateFinalPrice()}元 | 库存:${goods.stock}件`)
                  .fontSize(14)
                  .fontColor(Color.Green);

                // 子类专属信息(类型判断)
                if (goods instanceof BookGoods) {
                  Text(goods.getAuthorInfo())
                    .fontSize(14)
                    .fontColor(Color.Blue);
                }
                if (goods instanceof DigitalGoods) {
                  Text(`品牌:${goods.brand} | 保修:${goods.warranty}年`)
                    .fontSize(14)
                    .fontColor(Color.Orange);
                }
              }
              .padding(15)
              .backgroundColor(Color.White)
              .borderRadius(8)
              .shadow({ radius: 3, color: Color.Grey, offsetX: 2, offsetY: 2 })
              .width('100%')
            }.padding(15)
            .width('100%')
          }
        );
      }
      .width('100%')
      .layoutWeight(1);
    }
    .width('100%')
    .height('100%')
    .backgroundColor("#f5f5f5");
  }
}

4.1 核心说明

  1. 抽象类多态体现:无论子类是数码还是图书,都可通过AbstractGoods类型列表统一管理,调用calculateFinalPrice时自动执行子类的差异化实现;
  2. 类型判断规则:调用子类专属方法/属性时,需通过instanceof判断类型(编译器自动做静态检查,无需额外类型断言);
  3. UI定位说明:本节UI仅为“抽象类数据层”的可视化示例,基础阶段无需深入ArkUI语法,核心掌握“抽象类+多态”的逻辑即可。

4.2 运行效果说明

  1. 控制台会打印两类商品的完整信息,包含基础信息、子类专属信息和差异化计算的折后价;
  2. 点击“批量增加50件库存”按钮,库存会更新并刷新UI;
  3. 抽象类无法直接实例化的语法限制会在编译阶段生效,避免创建无业务意义的对象。

4.3 抽象类与普通类的核心差异

对比维度 普通类(原Goods类) 抽象类(AbstractGoods类)
实例化 可直接new创建对象 不可直接实例化,仅能被继承
方法包含 仅能包含有方法体的普通方法 可包含普通方法+无方法体的抽象方法
子类约束 子类可选择是否重写父类方法 子类必须实现所有抽象方法(否则为抽象类)
业务约束性 弱(可创建无意义的纯商品对象) 强(强制子类为具体商品类型,实现差异化逻辑)
工程化推荐场景 独立的业务实体(无子类扩展) 作为基类统一管理子类(如商品基类)

五、常见问题解答

5.1 抽象类可以有构造函数吗?

可以。抽象类的构造函数不能直接调用(因抽象类无法实例化),但子类构造函数必须通过super()调用抽象父类的构造函数,用于初始化父类属性,是代码复用的核心方式。

5.2 抽象方法可以有访问修饰符吗?

可以。抽象方法通常用publicprotectedprivate无意义,子类无法访问),子类实现时的访问修饰符权限不能低于父类(如父类是public,子类不能改为protected)。

5.3 为什么不直接用接口代替抽象类?

ArkTS中接口仅能定义属性签名、方法签名,无法包含方法实现、实例属性初始化和构造函数;抽象类可同时包含“可复用的普通逻辑”和“需子类实现的抽象逻辑”,更适合作为有通用属性/方法的业务基类(如商品基类)。

六、内容总结

  1. 抽象类通过abstract关键字定义,无法直接实例化,可包含普通属性/方法和抽象方法,解决了普通父类创建无意义对象的问题;
  2. 抽象方法无方法体,子类必须用override实现,强制子类差异化处理核心业务逻辑(如不同商品的折后价计算);
  3. 抽象类+多态可统一管理所有子类对象,结合极简ArkUI可实现商品列表的可视化渲染(UI仅为示例,核心是抽象类逻辑);
  4. 抽象类是“半抽象半具体”的基类,兼顾代码复用与业务约束,是鸿蒙开发中管理多子类场景的核心选型。

七、代码仓库

八、下节预告

本节通过抽象类解决了商品类“无意义实例化”和“核心逻辑强制实现”的问题,但当前设计仍存在两大痛点:

  1. 构造函数参数零散,易出现顺序/类型错误,后期新增字段维护成本高;
  2. 通用属性与子类独有属性混编,缺乏统一规范,扩展时易出现命名混乱。

下一节将聚焦商品类重构拓展,基于属性契约接口优化架构:

  1. 定义通用属性契约接口(IGoodsBase),规范所有商品的公共属性;
  2. 设计子类独有属性契约接口,通过extends继承通用接口,实现属性分层管理;
  3. 用结构化接口对象替代零散参数,从根源上规避传参错误;
  4. 重构商品类并完成实战验证,实现“逻辑与数据”解耦,提升代码健壮性。
posted @ 2026-01-22 12:30  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报