散修带你入门鸿蒙应用开发基础第十一节:面向对象思想入门与类的定义
第十一节:面向对象思想入门与类的定义
炼气十一重天
【学习目标】
- 理解“类(模板)-对象(实例)”的核心关系,区分面向过程与面向对象编程思维在鸿蒙开发中的适配场景;
- 掌握ArkTS类的标准定义语法(属性声明、构造函数、实例方法),熟练使用
new关键字实例化对象; - 吃透
this关键字的含义与使用场景,规避鸿蒙开发中this的常见使用错误; - 理解封装的核心意义,掌握
public/private访问修饰符(protected将在下一节继承章节讲解),实现敏感数据(如商品底价)的安全封装; - 掌握getter/setter方法的设计思路,理解其与普通方法的本质区别,实现鸿蒙应用中属性的安全读写。
【学习重点】
- ArkTS类“先声明、后赋值”的核心规则,区分与TypeScript的核心差异;
this关键字在构造函数/实例方法中的正确使用,规避“属性未绑定”错误;private修饰符在鸿蒙敏感数据场景(商品底价)的核心应用;- getter/setter方法与普通方法的本质区别,掌握
set/get关键字的设计初衷与选型原则; - 类内部方法与全局函数的语法差异(
function关键字的使用规则); - 面向对象封装思想在鸿蒙业务模型类(商品)中的落地。
一、工程结构
本节示例代码基于鸿蒙Stage模型工程(API 12+),工程名称为ClassObjectDemo,核心目录结构遵循鸿蒙开发规范:
ClassObjectDemo/
├── entry/ # 应用主模块
│ ├── src/
│ │ ├── main/
│ │ │ ├── ets/ # ArkTS代码根目录
│ │ │ │ ├── pages/ # 页面代码目录(补充完整路径)
│ │ │ │ │ └── Index.ets # 测试页面
│ │ │ │ └── model/ # 业务模型类目录
│ │ │ │ └── Goods.ets # 商品类
│ │ │ ├── resources/ # 资源目录(非本节重点)
│ │ │ └── module.json5 # 模块配置文件
│ │ └── oh-package.json5 # 工程依赖配置
└── hvigorfile.ts # 构建脚本(默认生成)
二、类与对象基础解析
2.1 面向过程 vs 面向对象
编程的核心是“处理数据+实现逻辑”,两种思维在鸿蒙开发中的适配性差异显著:
| 编程思维 | 核心特点 | 鸿蒙适配场景 | 缺点 |
|---|---|---|---|
| 面向过程 | 数据与方法分离,按步骤执行 | 简单逻辑(如单个商品信息打印) | 多实例场景代码冗余、维护性差 |
| 面向对象 | 数据与方法封装为类,实例化调用 | 多实例管理(商品) | 入门稍复杂,适配工程化开发 |
2.1.1 面向过程实现
// 定义数据
let product: string = "鸿蒙基础开发手册";
let price: number = 59.9;
// 封装打印方法(需手动传参)
function printGoodsInfo(name: string, price: number): void {
console.log(`商品名称:${name},价格:${price}元`);
}
// 执行逻辑
printGoodsInfo(product, price);
2.1.2 面向对象实现
步骤1:定义商品类(数据+行为封装)
// model/Goods.ets
export class Goods {
// 商品属性(数据封装)
name: string;
price: number;
category?: string;
// 构造函数:初始化属性(必须先声明后赋值)
constructor(name: string, price: number, category?: string) {
this.name = name;
this.price = price;
if (category) {
this.category = category;
}
}
// 实例方法:行为封装
printInfo(): void {
console.log(`商品名称:${this.name},价格:${this.price}元`);
}
}
步骤2:调用商品类
// pages/Index.ets 导入Goods
import { Goods } from '../model/Goods';
// 实例化多商品,自带属性和方法,无需重复传参
aboutToAppear(){
const phone = new Goods("鸿蒙Mate70手机", 5999.00, "数码产品");
const watch = new Goods("鸿蒙智能手表", 1299.00, "穿戴设备");
phone.printInfo(); // 直接调用,逻辑内聚
watch.printInfo();
}
2.2 核心概念解析
- 类(Class):描述一类事物的通用模板,定义了该类事物的共同属性和行为(如
Goods类定义了商品的名称、价格属性和打印信息方法); - 对象(Object):类的具体实例,是类模板的具象化(如
phone是Goods类的实例,对应具体的商品); - 封装:将数据(属性)和操作数据的逻辑(方法)打包到类中,外部仅通过暴露的接口操作数据,无需关心内部实现。
2.3 类的标准定义语法
ArkTS类遵循“先声明、后赋值”原则,不支持 TypeScript中“构造函数参数加public简化属性定义”的写法,标准模板:
export class 类名 {
// 1. 属性声明:指定类型,可选默认值
公共属性名: 数据类型;
带默认值属性名: 数据类型 = 默认值;
// 2. 构造函数(可选):实例化时初始化属性
constructor(参数1: 数据类型, 参数2?: 数据类型) { // 参数2为可选
this.公共属性名 = 参数1;
if (参数2) {
this.带默认值属性名 = 参数2;
}
}
// 3. 实例方法:描述对象行为
无返回值方法(): void {
console.log(this.公共属性名); // 访问属性必须加this
}
有返回值方法(参数: 数据类型): 返回值类型 {
return this.带默认值属性名 + 参数;
}
}
2.4 function关键字使用规则
核心规则(强制):
- 全局/独立函数:必须显式使用
function关键字(或箭头函数)声明,这是ArkTS的基础语法规则; - 类内部方法(实例方法、getter/setter、静态方法):禁止使用
function关键字,直接以“方法名(参数): 返回值类型”的形式声明,加function会直接触发语法报错。
// 示例1:全局函数(必须加function)
function globalPrintInfo(name: string): void {
console.log(`全局函数:${name}`);
}
// 示例2:类内部方法(无需加function)
class Goods {
name: string;
constructor(name: string) {
this.name = name;
}
// ✅ 正确:类内部方法无function关键字
printInfo(): void {
console.log(`类方法:${this.name}`);
}
// ❌ 错误:类内部方法加function关键字,语法报错
// function printError(): void {
// console.log(this.name);
// }
}
2.5 this关键字深度解析
this指向“当前对象实例”,仅在构造函数和实例方法中有效,核心作用是区分“类的属性”和“方法/构造函数的参数”。
2.6 常见错误及规避示例
// 错误示例对比
// ❌ ArkTS编译报错示例:未声明属性直接在构造函数赋值
class GoodsError {
constructor(name: string) {
this.name = name; // 报错:Property 'name' does not exist on type 'GoodsError'
}
}
// ✅ 正确写法:先声明、后赋值
class GoodsCorrect {
name: string; // 第一步:声明属性
constructor(name: string) {
this.name = name; // 第二步:构造函数赋值
}
}
export class Goods {
// 错误:ArkTS不允许构造函数参数加public简化属性声明
// constructor(public name: string, public price: number) {}
// 正确设置构造函数 有属性不可简化
name: string;
price: number;
constructor(name: string, price: number) {
// 错误:参数自赋值,类属性未初始化
// name = name;
// 正确
this.name = name;
this.price = price;
}
// 正确写法:类内部方法无function关键字
printInfo(): void {
// 错误:访问未定义的局部变量
// console.log(name);
// 正确
console.log(`商品名称:${this.name},价格:${this.price}元`);
}
}
三、封装思想及访问控制应用
3.1 封装的核心意义
本节所有封装案例均围绕鸿蒙电商核心的“商品类”展开,封装的核心目标是:将商品的核心数据(名称、底价、分类)与操作逻辑(价格校验、售价计算)打包为独立的业务模型,外部仅通过暴露的接口(getter/setter、普通方法)操作商品数据,既保证数据安全(如底价不被随意修改),又降低代码耦合度。
鸿蒙开发中需封装的场景:
- 电商类:商品底价、供应商信息。
3.2 访问修饰符规则
ArkTS提供三种核心访问修饰符,默认所有成员为public,用于控制属性/方法的访问范围:
| 修饰符 | 访问范围 | 鸿蒙应用场景 | 示例 |
|---|---|---|---|
| public | 类内、类外均可访问 | 普通非敏感数据(商品名称、分类) | public name: string; |
| private | 仅类内可访问 | 敏感数据(商品底价) | private _basePrice: number; |
| protected | 类内、子类可访问,类外不可 | 层级类中间数据(下一节《继承基础与多态入门》 详细讲解) | protected config: string; |
3.3 private修饰符实战
以商品底价为例,用private隐藏敏感数据,仅通过公共方法/getter/setter实现受控访问:
四、getter/setter方法(安全读写属性)
对于需对外暴露但需校验的属性(如商品底价),通过getter/setter实现“校验式读写”,同时深度解析set关键字的设计初衷、与普通方法的本质区别及选型原则。
4.1 实战示例(商品底价管理)
// model/Goods.ets
export class Goods {
public name: string;
// 步骤1:声明时设置默认值“商品分类”,未传入参数时生效
public category: string = "商品分类";
private _basePrice: number; // 私有底价(加下划线区分setter名,避免重名)
constructor(name: string, basePrice: number, category?: string) {
// 可选优化:对名称做非空校验(新手可自行补充,如this.name = name.trim() || "未命名商品")
this.name = name;
// 校验:底价不能为负
this._basePrice = basePrice > 0 ? basePrice : 0;
// 步骤2:传入合法的category参数时覆盖默认值,否则保留默认值
if (category?.trim()) {
this.category = category.trim();
}
}
// setter:受控修改底价(仅负责单个属性的校验与修改)
set basePrice(newPrice: number) {
if (newPrice < 0) {
console.log("商品底价不能为负,修改失败");
return;
}
this._basePrice = newPrice;
console.log(`${this.name}底价修改为:${newPrice}元`);
}
// getter:受控读取底价
get basePrice() {
return this._basePrice;
}
}
// 【辅助测试函数】:仅用于快速验证类的功能,实际开发中直接实例化Goods类即可
export function testGoodsClass() {
// 实例化商品并测试底价读写
const phone = new Goods("鸿蒙手机", 5999.00, "数码产品");
console.log("商品名称:", phone.name); // 合法访问public属性
console.log("初始底价:", phone.basePrice); // 通过getter读取底价
phone.basePrice = 4999; // 通过setter修改底价(合法)
console.log("修改后底价:", phone.basePrice);
phone.basePrice = -100; // 校验失败,底价不修改
// 错误:直接访问private属性,编译报错
// console.log(phone._basePrice);
}
4.2 调用测试
// pages/Index.ets
import { testGoodsClass, Goods } from '../model/Goods';
aboutToAppear(){
// 1. 辅助测试函数调用(快速验证功能)
testGoodsClass();
// 2. 实际开发场景:直接实例化Goods类
const watch = new Goods("鸿蒙智能手表", 1299.00, "穿戴设备");
watch.basePrice = 1199;
console.log(`智能手表底价:${watch.basePrice}`);
}
4.3 深度解析:set、get关键字的核心价值
set(及get)是ArkTS/TypeScript为“属性的受控读写”设计的专用语法,并非普通方法的“语法糖”,其核心价值体现在语法特性、语义设计、工程规范三个维度:
setter的核心配套价值是与getter联动,让私有属性的“读”和“写”都以属性语法暴露,同时隐藏内部实现:
这种模式下,外部调用者无需关心“属性是私有还是公共”,只需按统一的属性语法操作,而开发者可在get/set中封装所有读写逻辑,符合“开闭原则”(扩展开放、修改封闭)。在鸿蒙电商应用中,商品底价的setter可统一接入价格风控逻辑,无需修改外部调用代码,这就是开闭原则的落地。
4.3.1 对比普通方法与setter/getter
// 语法特性:属性式操作
class Goods {
name: string;
private _basePrice: number;
constructor(name: string, basePrice: number) {
this.name = name;
this._basePrice = basePrice > 0 ? basePrice : 0;
}
// ① 普通方法:通用方法调用语法
setBasePriceNormal(newPrice: number) {
if (newPrice < 0) {
console.log("商品底价不能为负,修改失败");
return;
}
this._basePrice = newPrice;
}
// ② setter方法:属性赋值语法
set basePrice(newPrice: number) {
if (newPrice < 0) {
console.log("商品底价不能为负,修改失败");
return;
}
this._basePrice = newPrice;
}
// getter:受控读取属性
get basePrice() {
return this._basePrice;
}
}
export function testGoodsClass(){
// 调用对比
const phone = new Goods("鸿蒙手机", 5999.00);
// 普通方法:方法名(参数)修改价格
phone.setBasePriceNormal(3999);
console.log(`普通方法设置鸿蒙手机底价:${phone.basePrice}`); // 输出:3999
// setter:对象.属性 = 值 修改价格
phone.basePrice = 4999;
console.log(`setter设置鸿蒙手机底价:${phone.basePrice}`); // 输出:4999
}
- 普通方法:遵循“方法调用”语法,强调“执行一个动作”;
- setter方法:遵循“属性赋值”语法,强调“修改一个属性”,贴合面向对象中“属性封装”的设计思想。
如果_basePrice直接可以访问、数值可随意修改,无数值安全验证输出负数都可以。使用set既满足了数值安全设置,又可以使用属性赋值操作。
4.3.2 明确的职责边界,提升代码可读性与可维护性
| 特性 | 普通方法 | setter方法 |
|---|---|---|
| 职责范围 | 可包含任意逻辑(修改属性、调用接口、日志记录等) | 唯一职责:受控修改单个属性 |
| 代码可读性 | 需阅读方法体才能确定核心逻辑 | 见名知意,set前缀直接表明“修改属性” |
| 工程规范性 | 无强制约束,易出现职责混乱 | 符合面向对象“单一职责”原则 |
4.3.3 选型原则(通用,适用于所有开发场景)
| 场景 | 推荐方案 | 核心依据 |
|---|---|---|
| 仅需受控修改单个属性(含校验/简单逻辑) | setter方法 | 语义清晰、符合属性封装设计 |
| 方法包含多逻辑(修改属性+调用接口/日志/多属性修改) | 普通方法 | 避免setter职责过载 |
| 属性需对外暴露“读+写”能力 | getter+setter | 统一语法,完整封装读写逻辑 |
| 属性仅需“读”能力,无需修改 | 仅getter方法 | 隐藏修改入口,保证数据安全 |
五、核心案例:鸿蒙商品管理类
整合封装、this、访问控制,实现商品底价的安全管理:
// model/Goods.ets
export class Goods {
public name: string;
// 新增:商品分类属性,设置默认值,未传入category时生效
public category: string = "商品分类";
private _basePrice: number; // 私有底价
// 新增:利润利率设为类属性,支持外部灵活调整(默认10%)
public profitRate: number = 0.1;
// 可选属性`?`放在最后(必须遵守)
constructor(name: string, basePrice: number, category?: string) {
this.name = name
// 可选优化:
this.category = category?.trim() || this.category
// 校验:底价不能为负(基础数值校验)
this._basePrice = basePrice > 0 ? basePrice : 0;
// 传入合法的category时覆盖默认值,否则保留默认值
this.category = category || this.category
}
// 普通方法-修改底价(用于对比setter)
setBasePriceNormal(newPrice: number) {
if (newPrice < 0) {
console.log("商品底价不能为负,修改失败");
return;
}
this._basePrice = newPrice;
}
// setter:修改底价(单一职责:仅受控修改底价)
set basePrice(newPrice: number) {
if (newPrice < 0) {
console.log("商品底价不能为负,修改失败");
return;
}
this._basePrice = newPrice;
console.log(`${this.name}底价修改为:${newPrice}元`);
}
// getter:读取底价
get basePrice() {
return this._basePrice;
}
// 普通方法:多逻辑组合(计算售价+数据格式化)
calculateSellingPrice(): number {
// 利润利率从类属性读取,支持外部调整
const sellingPrice = this._basePrice * (1 + this.profitRate);
return parseFloat(sellingPrice.toFixed(2));
}
// 普通方法:多逻辑组合(打印详情+数据校验)
printBaseInfo(): void {
if (!this.name) console.log("商品名称未设置");
console.log(`
商品名称:${this.name}
商品分类:${this.category}
商品售价:${this.calculateSellingPrice()}元
`);
}
}
Index页面直接创建类、调用方法
// pages/Index.ets 测试调用(实际开发场景,推荐)
import { Goods } from '../model/Goods';
aboutToAppear() {
const phone = new Goods("鸿蒙手机", 5999.00, "数码产品");
// 普通方法修改底价
phone.setBasePriceNormal(3999);
console.log(`普通方法设置鸿蒙手机底价:${phone.basePrice}`); // 输出:3999
console.log(`商品售价:${phone.calculateSellingPrice()}`); // 4398.9
// 调整利润利率(数码产品设为15%)
phone.profitRate = 0.15;
// setter修改底价
phone.basePrice = 4999;
console.log(`setter设置鸿蒙手机底价:${phone.basePrice}`); // 输出:4999
console.log(`商品售价:${phone.calculateSellingPrice()}`); // 5748.85
// 打印商品详情
phone.printBaseInfo();
// 测试category默认值:未传入category时使用“商品分类”
const book = new Goods("鸿蒙开发手册", 59.9);
console.log(`图书分类:${book.category}`); // 输出:商品分类
}
运行预览Log日志

六、常见问题解答
6.1 类可以没有构造函数吗?
可以。ArkTS会自动生成空构造函数,但未设置默认值的属性必须在构造函数中赋值,否则编译报错:
// model/Goods.ets
export class EmptyClass {
msg: string = "默认信息"; // 设默认值,无构造函数也合法
}
// pages/Index.ets 调用测试(补充路径)
import { EmptyClass } from '../model/Goods';
const obj = new EmptyClass();
console.log(obj.msg); // 输出:默认信息
6.2 多个对象实例的属性是否相互独立?
是。每个对象拥有独立的内存空间,修改一个对象的属性不会影响其他对象:
// pages/Index.ets 调用测试(补充导入)
import { Goods } from '../model/Goods';
const phone = new Goods("鸿蒙手机", 2999);
const watch = new Goods("鸿蒙手表", 299);
phone.basePrice = 2899;
console.log(`phone售价:${phone.calculateSellingPrice()}`); // 3187.9
console.log(`watch售价:${watch.calculateSellingPrice()}`); // 328.9
6.3 getter/setter和普通方法的本质区别?
- 语法层面:getter/setter遵循“属性语法”,普通方法遵循“方法调用语法”;
- 语义层面:getter/setter专注于“单个属性的受控读写”,普通方法可包含任意逻辑;
- 设计层面:getter/setter符合“单一职责”“属性封装”等面向对象设计原则,普通方法无强制约束;
- 能力层面:getter/setter可联动实现完整的属性读写封装,普通方法需手动维护读写逻辑的一致性。
七、课堂小结
- 面向对象通过“类+对象”封装数据和方法,解决面向过程多实例代码冗余问题,核心是“数据与行为的绑定”;
- ArkTS类需遵循“先声明、后赋值”原则,不支持TypeScript构造函数参数加
public的简化写法,this关键字是访问实例属性的唯一合法方式; function关键字的强制规则:全局/独立函数必须显式声明,类内部方法禁止使用,加function会直接语法报错;- 封装的核心是用
private隐藏敏感数据(如商品底价),仅通过受控接口暴露操作能力,public/private明确了属性/方法的访问边界; set/get关键字并非普通方法的语法糖,而是面向对象“属性封装”的专用语法,核心价值是:- 语法层面:属性式操作,贴合属性读写的直觉;
- 语义层面:单一职责,明确区分“属性修改”与“通用逻辑执行”;
- 设计层面:与
get联动实现完整的属性读写封装,符合开闭原则;
- setter与普通方法的选型需遵循“单一职责”原则:仅受控修改单个属性用setter,包含多逻辑组合用普通方法,该原则适用于所有开发场景;
- 下一节将通过继承扩展商品类,学习
protected修饰符和子类与父类的协同逻辑,实现鸿蒙商品分类的统一管理。
八、代码仓库
本节代码已同步至:https://gitee.com/juhetianxia321/harmony-os-code-base.git
九、下节预告
- 下一节:继承基础与多态入门将深入学习面向对象进阶核心:
- 类的继承规则:
extends关键字、super调用父类构造/方法的核心语法; protected修饰符实战:子类复用父类中间数据(如商品底价);- 多态入门:父类引用管理子类对象,实现鸿蒙商品分类逻辑的统一管理;
- 类的继承规则:
- 继承与多态是鸿蒙电商场景“商品分类管理”的核心设计思想,掌握后将为后续抽象类、接口学习打下基础,最终通过“鸿蒙电商商品管理逻辑”整合所有知识点,实现完整业务流程。
十、鸿蒙开发者学习与认证指引
(一)、官方学习班级报名(免费)
- 班级链接:HarmonyOS赋能资源丰富度建设(第四期)
- 学号填写规则:填写个人手机号码即可完成班级信息登记
(二)、HarmonyOS应用开发者认证考试(免费)
-
考试链接:HarmonyOS开发者能力认证入口
-
认证等级及适配人群
- 基础认证:适配软件工程师、移动应用开发人员,需掌握HarmonyOS基础概念、DevEco Studio基础使用、ArkTS及ArkUI基础开发等能力;
- 高级认证:适配项目经理、工程架构师,需掌握系统核心技术理念、应用架构设计、关键技术开发及应用上架运维等能力;
- 专家认证:适配研发经理、解决方案专家,需掌握分布式技术原理、端云一体化开发、跨端迁移及性能优化等高级能力。
-
认证权益:通过认证可获得电子版证书以及其他专属权益。
浙公网安备 33010602011771号