第一章:ArkTS面向对象编程——从基础到实战

大家好,我是老张。今天咱们聊聊ArkTS的面向对象编程。说实话,很多初学者觉得面向对象就是个概念,背背定义就完事了。但我在鸿蒙项目里踩过不少坑,才真正理解——面向对象不是理论,是帮你管理复杂代码的工具。

ArkTS基于TypeScript,但做了不少鸿蒙特有的优化。咱们直接上干货。

1. 类的定义:从最简单的开始

先看一个最基础的类定义。我个人习惯把类当作“模板”,用来创建对象。

class Person {
  name: string = ''
  age: number = 0

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  sayHello(): void {
    console.log(`你好,我是${this.name},今年${this.age}岁`)
  }
}

let p = new Person('张三', 25)
p.sayHello()

这里要注意几点:

  • 属性必须初始化,或者用!声明非空。我刚开始写ArkTS时老忘,编译器直接报错。
  • constructor是构造函数,创建对象时自动调用。
  • 方法里用this访问当前实例的属性。
小技巧: 如果属性很多,可以用可选参数简化构造函数。比如 constructor(name: string, age?: number),这样age可以不传。

3. 继承:别重复造轮子

继承是面向对象的核心之一。说白了,就是子类复用父类的代码。

class Student extends Person {
  grade: number = 0

  constructor(name: string, age: number, grade: number) {
    super(name, age)  // 调用父类构造函数
    this.grade = grade
  }

  study(): void {
    console.log(`${this.name}正在学习,年级:${this.grade}`)
  }
}

let stu = new Student('李四', 20, 3)
stu.sayHello()  // 继承自父类
stu.study()     // 子类自己的方法

嗯,这里有个坑。我曾经在项目里忘了调用super(),结果子类属性全是undefined。记住:子类构造函数里,super()必须写在第一行。

注意: ArkTS不支持多继承。一个子类只能继承一个父类。想实现类似效果?用接口或者组合。

3. 接口:定义契约

接口是什么?你可以把它理解成一份“合同”。谁实现了这个接口,就必须遵守里面的规则。

interface Flyable {
  fly(): void
  land(): void
}

class Bird implements Flyable {
  fly(): void {
    console.log('鸟儿在飞')
  }

  land(): void {
    console.log('鸟儿着陆')
  }
}

class Airplane implements Flyable {
  fly(): void {
    console.log('飞机起飞')
  }

  land(): void {
    console.log('飞机降落')
  }
}

你想想看,如果没有接口,每个类自己定义方法名,调用方得多痛苦?接口统一了行为规范。

我在项目中经常用接口来定义数据模型。比如网络请求返回的数据结构,用接口约束好,后面处理起来特别清晰。

4. 泛型:让代码更灵活

泛型,说白了就是“类型参数”。写一个函数或类,可以处理多种类型,而不是写死。

// 泛型函数
function identity<T>(arg: T): T {
  return arg
}

let result1 = identity<number>(42)
let result2 = identity<string>('hello')

// 泛型类
class Box<T> {
  private content: T

  constructor(content: T) {
    this.content = content
  }

  getContent(): T {
    return this.content
  }
}

let numberBox = new Box<number>(123)
let stringBox = new Box<string>('abc')

为什么会用到泛型?举个例子。我做过一个缓存工具,要存各种类型的数据。没有泛型的话,要么用any(失去类型检查),要么每种类型写一个类(代码爆炸)。泛型完美解决了这个问题。

核心要点: 泛型不是“万能药”,但能显著减少重复代码。记住:<T>只是一个占位符,你可以用任何字母,但业界习惯用T、K、V。

5. 模块化:import/export

模块化是大型项目的基石。ArkTS支持ES Module语法,用export导出,import导入。

// math.ts
export function add(a: number, b: number): number {
  return a + b
}

export const PI = 3.14159

export class Calculator {
  multiply(a: number, b: number): number {
    return a * b
  }
}

// main.ts
import { add, PI, Calculator } from './math'

console.log(add(1, 2))
console.log(PI)
let calc = new Calculator()
console.log(calc.multiply(3, 4))

我个人习惯用命名导出(就是带花括号那种)。默认导出虽然方便,但重构时容易出问题。

避坑指南: 我曾经在项目里把export defaultexport混用,结果导入时搞不清要不要加花括号。后来统一用命名导出,再也没出过问题。

6. 综合实战:一个简单的用户管理系统

咱们把今天学的知识串起来,写个小例子。

// 定义接口
interface User {
  id: number
  name: string
  email: string
}

// 泛型仓库
class Repository<T> {
  private items: T[] = []

  add(item: T): void {
    this.items.push(item)
  }

  getAll(): T[] {
    return this.items
  }

  find(predicate: (item: T) => boolean): T | undefined {
    return this.items.find(predicate)
  }
}

// 继承扩展
class UserRepository extends Repository<User> {
  findByEmail(email: string): User | undefined {
    return this.find(user => user.email === email)
  }
}

// 使用
let userRepo = new UserRepository()
userRepo.add({ id: 1, name: '张三', email: 'zhangsan@example.com' })
userRepo.add({ id: 2, name: '李四', email: 'lisi@example.com' })

console.log(userRepo.findByEmail('lisi@example.com'))

这个例子涵盖了类、继承、接口、泛型、模块化(虽然没展示导入导出,但实际项目里每个类可以单独一个文件)。

嗯,到这里第一章的内容就差不多了。面向对象编程不是一蹴而就的,需要在实际项目中慢慢体会。下一章咱们会深入ArkTS的高级特性,包括装饰器、异步编程等。

记住:写代码不是炫技,是解决问题。面向对象只是工具,用好了事半功倍。


公众号:蓝海资料掘金营,微信deep3321