ArkTS基礎接口與類定義

文章概述

本文是ArkTS語言的基礎教程,重點講解在HarmonyOS應用開發中如何使用ArkTS定義類、接口以及相關的面向對象編程概念。通過本文,您將掌握ArkTS的核心語法特性,為構建複雜的HarmonyOS應用打下堅實基礎。

官方參考資料:

  • ArkTS語言官方文檔
  • HarmonyOS應用開發指南
  • ArkTS API參考

基礎概念

什麼是ArkTS?

ArkTS是HarmonyOS優選的應用開發語言,它在TypeScript的基礎上,擴展了聲明式UI、狀態管理等能力。

主要特性:

  • 靜態類型檢查
  • 面向對象編程支持
  • 聲明式UI描述
  • 狀態驅動UI更新
  • 與HarmonyOS API深度集成

環境要求

開發環境配置:

  • DevEco Studio 4.0或更高版本
  • HarmonyOS SDK API 9或更高版本
  • 推薦使用Node.js 16.x或18.x

類定義基礎

基本類定義

在ArkTS中,使用class關鍵字定義類,類可以包含屬性、構造器和方法。

// 基礎類定義示例
class Person {
  // 類屬性
  name: string
  age: number
  
  // 構造器
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  
  // 類方法
  introduce(): string {
    return `大家好,我是${this.name},今年${this.age}歲。`
  }
  
  // 帶參數的方法
  celebrateBirthday(): void {
    this.age++
    console.log(`${this.name}過生日啦!現在${this.age}歲。`)
  }
}

// 使用示例
@Entry
@Component
struct ClassExample {
  build() {
    Column() {
      Button('創建Person實例')
        .onClick(() => {
          let person = new Person('張三', 25)
          console.log(person.introduce())
          person.celebrateBirthday()
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

構造器參數屬性

ArkTS支持在構造器參數中直接定義屬性,這是定義類的簡潔方式。

// 使用構造器參數屬性
class Student {
  // 直接在構造器參數中定義屬性
  constructor(
    public name: string,
    public studentId: string,
    private grade: number = 1  // 默認參數
  ) {}
  
  getStudentInfo(): string {
    return `學生:${this.name},學號:${this.studentId},年級:${this.grade}`
  }
  
  promote(): void {
    this.grade++
    console.log(`${this.name}升到${this.grade}年級`)
  }
}

// 使用示例
@Entry
@Component
struct StudentExample {
  build() {
    Column() {
      Button('創建學生')
        .onClick(() => {
          let student = new Student('李四', '2024001')
          console.log(student.getStudentInfo())
          student.promote()
        })
    }
    .width('100%')
    .height('100%')
  }
}

接口定義與應用

基礎接口定義

接口用於定義對象的形狀,確保類實現特定的結構。

// 定義接口
interface Animal {
  name: string
  age: number
  makeSound(): void
  move(): void
}

// 實現接口的類
class Dog implements Animal {
  name: string
  age: number
  breed: string
  
  constructor(name: string, age: number, breed: string) {
    this.name = name
    this.age = age
    this.breed = breed
  }
  
  makeSound(): void {
    console.log(`${this.name}在汪汪叫!`)
  }
  
  move(): void {
    console.log(`${this.name}在奔跑`)
  }
  
  // 類特有的方法
  fetch(): void {
    console.log(`${this.name}在接飛盤`)
  }
}

class Cat implements Animal {
  name: string
  age: number
  color: string
  
  constructor(name: string, age: number, color: string) {
    this.name = name
    this.age = age
    this.color = color
  }
  
  makeSound(): void {
    console.log(`${this.name}在喵喵叫!`)
  }
  
  move(): void {
    console.log(`${this.name}在優雅地走路`)
  }
  
  climbTree(): void {
    console.log(`${this.name}在爬樹`)
  }
}

可選屬性和只讀屬性

接口支持可選屬性和只讀屬性,提供更靈活的類型定義。

// 包含可選和只讀屬性的接口
interface Vehicle {
  readonly id: string        // 只讀屬性
  brand: string
  model: string
  year?: number             // 可選屬性
  start(): void
  stop(): void
}

class Car implements Vehicle {
  readonly id: string
  brand: string
  model: string
  year?: number
  
  constructor(id: string, brand: string, model: string, year?: number) {
    this.id = id
    this.brand = brand
    this.model = model
    this.year = year
  }
  
  start(): void {
    console.log(`${this.brand} ${this.model} 啓動`)
  }
  
  stop(): void {
    console.log(`${this.brand} ${this.model} 停止`)
  }
  
  // 嘗試修改只讀屬性會報錯
  // updateId(newId: string): void {
  //   this.id = newId  // 編譯錯誤:無法分配到"id",因為它是隻讀屬性
  // }
}

繼承與多態

類繼承

ArkTS支持類的單繼承,使用extends關鍵字。

// 基類
class Shape {
  color: string
  protected position: { x: number, y: number }  // 受保護屬性
  
  constructor(color: string, x: number = 0, y: number = 0) {
    this.color = color
    this.position = { x, y }
  }
  
  // 基礎方法
  move(x: number, y: number): void {
    this.position.x = x
    this.position.y = y
    console.log(`圖形移動到位置 (${x}, ${y})`)
  }
  
  // 抽象方法(在TypeScript中需要使用abstract關鍵字,但ArkTS基於TS)
  getArea(): number {
    return 0
  }
  
  describe(): string {
    return `這是一個${this.color}的圖形`
  }
}

// 派生類
class Circle extends Shape {
  radius: number
  
  constructor(color: string, radius: number, x: number = 0, y: number = 0) {
    super(color, x, y)  // 調用父類構造器
    this.radius = radius
  }
  
  // 重寫父類方法
  getArea(): number {
    return Math.PI * this.radius * this.radius
  }
  
  describe(): string {
    return `${super.describe()},是一個半徑為${this.radius}的圓形,面積:${this.getArea().toFixed(2)}`
  }
  
  // 子類特有方法
  getCircumference(): number {
    return 2 * Math.PI * this.radius
  }
}

class Rectangle extends Shape {
  width: number
  height: number
  
  constructor(color: string, width: number, height: number, x: number = 0, y: number = 0) {
    super(color, x, y)
    this.width = width
    this.height = height
  }
  
  getArea(): number {
    return this.width * this.height
  }
  
  describe(): string {
    return `${super.describe()},是一個${this.width}x${this.height}的矩形,面積:${this.getArea()}`
  }
  
  // 子類特有方法
  isSquare(): boolean {
    return this.width === this.height
  }
}

多態應用

@Entry
@Component
struct ShapeExample {
  build() {
    Column() {
      Button('多態演示')
        .onClick(() => {
          // 多態:父類引用指向子類對象
          let shapes: Shape[] = [
            new Circle('紅色', 5),
            new Rectangle('藍色', 4, 6),
            new Circle('綠色', 3)
          ]
          
          shapes.forEach(shape => {
            console.log(shape.describe())
          })
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

訪問控制修飾符

ArkTS提供了三種訪問控制修飾符,用於控制類成員的可見性。

修飾符

類內部

子類

類外部

描述

public




默認修飾符,任何地方都可訪問

private




僅在類內部可訪問

protected




類內部和子類可訪問

class BankAccount {
  public readonly accountNumber: string
  private _balance: number = 0
  protected owner: string
  
  constructor(accountNumber: string, owner: string, initialBalance: number = 0) {
    this.accountNumber = accountNumber
    this.owner = owner
    this._balance = initialBalance
  }
  
  // 公共方法 - 可以外部調用
  public deposit(amount: number): void {
    if (amount > 0) {
      this._balance += amount
      console.log(`存入${amount}元,當前餘額:${this._balance}元`)
    }
  }
  
  public withdraw(amount: number): boolean {
    if (amount > 0 && this._balance >= amount) {
      this._balance -= amount
      console.log(`取出${amount}元,當前餘額:${this._balance}元`)
      return true
    }
    console.log('取款失敗:餘額不足或金額無效')
    return false
  }
  
  // 私有方法 - 只能在類內部使用
  private validateAmount(amount: number): boolean {
    return amount > 0 && amount <= 1000000
  }
  
  // 受保護方法 - 子類可以訪問
  protected updateOwner(newOwner: string): void {
    this.owner = newOwner
    console.log(`賬户持有人變更為:${newOwner}`)
  }
  
  // 公開的getter方法
  public get balance(): number {
    return this._balance
  }
}

// 繼承示例
class SavingsAccount extends BankAccount {
  private interestRate: number
  
  constructor(accountNumber: string, owner: string, interestRate: number) {
    super(accountNumber, owner)
    this.interestRate = interestRate
  }
  
  // 可以訪問父類的protected成員
  transferOwnership(newOwner: string): void {
    this.updateOwner(newOwner)  // 可以調用protected方法
  }
  
  // 無法訪問父類的private成員
  // showPrivateBalance(): void {
  //   console.log(this._balance)  // 編譯錯誤:_balance是私有的
  // }
  
  addInterest(): void {
    const interest = this.balance * this.interestRate  // 通過public getter訪問
    this.deposit(interest)
    console.log(`添加利息:${interest.toFixed(2)}元`)
  }
}

@Entry
@Component
struct BankExample {
  build() {
    Column() {
      Button('銀行賬户演示')
        .onClick(() => {
          let account = new SavingsAccount('123456789', '王五', 0.05)
          account.deposit(1000)
          account.addInterest()
          console.log(`當前餘額:${account.balance}元`)
          
          // 以下操作會報錯:
          // account._balance = 9999  // 錯誤:_balance是私有的
          // account.updateOwner('趙六')  // 錯誤:updateOwner是受保護的
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

靜態成員

靜態成員屬於類本身,而不是類的實例。

class MathUtils {
  // 靜態屬性
  static readonly PI: number = 3.14159
  static readonly VERSION: string = '1.0.0'
  
  // 靜態方法
  static calculateCircleArea(radius: number): number {
    return this.PI * radius * radius
  }
  
  static calculateDistance(x1: number, y1: number, x2: number, y2: number): number {
    return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
  }
  
  static getVersion(): string {
    return `數學工具庫版本:${this.VERSION}`
  }
}

class Counter {
  private static instanceCount: number = 0
  private static _totalCount: number = 0
  
  public readonly id: number
  
  constructor() {
    Counter.instanceCount++
    this.id = Counter.instanceCount
  }
  
  // 靜態getter
  static get totalInstances(): number {
    return this.instanceCount
  }
  
  static get totalCount(): number {
    return this._totalCount
  }
  
  increment(): void {
    Counter._totalCount++
    console.log(`計數器${this.id}增加,總計:${Counter._totalCount}`)
  }
  
  decrement(): void {
    if (Counter._totalCount > 0) {
      Counter._totalCount--
      console.log(`計數器${this.id}減少,總計:${Counter._totalCount}`)
    }
  }
}

@Entry
@Component
struct StaticExample {
  build() {
    Column() {
      Button('靜態成員演示')
        .onClick(() => {
          // 使用靜態方法,不需要創建實例
          console.log(`PI值:${MathUtils.PI}`)
          console.log(`半徑為5的圓面積:${MathUtils.calculateCircleArea(5).toFixed(2)}`)
          console.log(MathUtils.getVersion())
          
          // 計數器實例演示
          let counter1 = new Counter()
          let counter2 = new Counter()
          
          counter1.increment()
          counter2.increment()
          counter1.increment()
          counter1.decrement()
          
          console.log(`創建的計數器實例數:${Counter.totalInstances}`)
          console.log(`總計數值:${Counter.totalCount}`)
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

抽象類和接口的高級用法

抽象類

抽象類不能實例化,只能被繼承,可以包含抽象方法和具體實現。

// 抽象類
abstract class Notification {
  protected recipient: string
  protected message: string
  
  constructor(recipient: string, message: string) {
    this.recipient = recipient
    this.message = message
  }
  
  // 抽象方法 - 必須由子類實現
  abstract send(): boolean
  
  // 具體方法 - 子類可以繼承使用
  protected formatMessage(): string {
    return `給 ${this.recipient} 的消息:${this.message}`
  }
  
  // 模板方法模式
  processNotification(): void {
    console.log('開始處理通知...')
    const success = this.send()
    if (success) {
      console.log('通知發送成功')
      this.logNotification()
    } else {
      console.log('通知發送失敗')
    }
  }
  
  private logNotification(): void {
    console.log(`通知記錄:${new Date().toISOString()} - ${this.formatMessage()}`)
  }
}

// 具體實現類
class EmailNotification extends Notification {
  private emailAddress: string
  
  constructor(recipient: string, message: string, emailAddress: string) {
    super(recipient, message)
    this.emailAddress = emailAddress
  }
  
  send(): boolean {
    console.log(`發送郵件到:${this.emailAddress}`)
    console.log(`郵件內容:${this.formatMessage()}`)
    // 模擬發送邏輯
    return Math.random() > 0.2  // 80%成功率
  }
}

class SMSNotification extends Notification {
  private phoneNumber: string
  
  constructor(recipient: string, message: string, phoneNumber: string) {
    super(recipient, message)
    this.phoneNumber = phoneNumber
  }
  
  send(): boolean {
    console.log(`發送短信到:${this.phoneNumber}`)
    console.log(`短信內容:${this.message}`)  // 短信通常較短,不使用完整格式
    // 模擬發送邏輯
    return Math.random() > 0.3  // 70%成功率
  }
}

接口繼承

接口可以繼承其他接口,實現接口的組合。

// 基礎接口
interface Identifiable {
  readonly id: string
  createdAt: Date
}

interface Timestampable {
  updatedAt: Date
  updateTimestamp(): void
}

// 接口繼承
interface User extends Identifiable, Timestampable {
  username: string
  email: string
  isActive: boolean
  activate(): void
  deactivate(): void
}

// 實現複合接口
class SystemUser implements User {
  readonly id: string
  createdAt: Date
  updatedAt: Date
  username: string
  email: string
  isActive: boolean = true
  
  constructor(id: string, username: string, email: string) {
    this.id = id
    this.username = username
    this.email = email
    this.createdAt = new Date()
    this.updatedAt = new Date()
  }
  
  updateTimestamp(): void {
    this.updatedAt = new Date()
    console.log(`用户 ${this.username} 時間戳已更新`)
  }
  
  activate(): void {
    this.isActive = true
    this.updateTimestamp()
    console.log(`用户 ${this.username} 已激活`)
  }
  
  deactivate(): void {
    this.isActive = false
    this.updateTimestamp()
    console.log(`用户 ${this.username} 已停用`)
  }
  
  getUserInfo(): string {
    return `用户ID:${this.id},用户名:${this.username},狀態:${this.isActive ? '活躍' : '停用'}`
  }
}

實際應用案例:HarmonyOS UI組件類

下面展示如何在HarmonyOS應用中使用ArkTS類來管理UI狀態。

// 定義數據模型類
class TodoItem {
  constructor(
    public id: string,
    public title: string,
    public completed: boolean = false,
    public createdAt: Date = new Date()
  ) {}
  
  toggleComplete(): void {
    this.completed = !this.completed
  }
  
  updateTitle(newTitle: string): void {
    this.title = newTitle
  }
}

// 定義狀態管理類
@Observed
class TodoList {
  private items: TodoItem[] = []
  
  addItem(title: string): void {
    const newItem = new TodoItem(
      Date.now().toString(),
      title
    )
    this.items.push(newItem)
  }
  
  removeItem(id: string): void {
    this.items = this.items.filter(item => item.id !== id)
  }
  
  toggleItem(id: string): void {
    const item = this.items.find(item => item.id === id)
    if (item) {
      item.toggleComplete()
    }
  }
  
  get completedCount(): number {
    return this.items.filter(item => item.completed).length
  }
  
  get totalCount(): number {
    return this.items.length
  }
  
  getItems(): TodoItem[] {
    return [...this.items]  // 返回副本
  }
}

@Entry
@Component
struct TodoApp {
  @State todoList: TodoList = new TodoList()
  @State newItemTitle: string = ''
  
  build() {
    Column({ space: 20 }) {
      Text('待辦事項')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      
      Text(`總計:${this.todoList.totalCount},已完成:${this.todoList.completedCount}`)
        .fontSize(16)
      
      TextInput({ placeholder: '輸入新事項' })
        .width('90%')
        .onChange((value: string) => {
          this.newItemTitle = value
        })
      
      Button('添加事項')
        .width('50%')
        .onClick(() => {
          if (this.newItemTitle.trim()) {
            this.todoList.addItem(this.newItemTitle)
            this.newItemTitle = ''
          }
        })
      
      List({ space: 10 }) {
        ForEach(this.todoList.getItems(), (item: TodoItem) => {
          ListItem() {
            Row({ space: 10 }) {
              Text(item.title)
                .fontSize(18)
                .decoration({ type: item.completed ? TextDecorationType.LineThrough : TextDecorationType.None })
              
              Checkbox()
                .select(item.completed)
                .onChange((checked: boolean) => {
                  this.todoList.toggleItem(item.id)
                })
              
              Button('刪除')
                .onClick(() => {
                  this.todoList.removeItem(item.id)
                })
            }
            .width('100%')
            .justifyContent(FlexAlign.SpaceBetween)
          }
        }, (item: TodoItem) => item.id)
      }
      .layoutWeight(1)
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

重要注意事項

版本兼容性

API級別説明:

  • 本文代碼基於HarmonyOS API 9編寫
  • 確保您的DevEco Studio和SDK版本兼容
  • 部分高級特性可能需要更高API級別

常見陷阱與解決方案

1. 構造器中的屬性初始化

// 錯誤示例
class ProblemClass {
  name: string
  // 忘記在構造器中初始化name
  
  constructor() {
    // 沒有初始化name
  }
}

// 正確做法
class CorrectClass {
  name: string = ''  // 提供默認值
  
  constructor(name?: string) {
    if (name) {
      this.name = name
    }
  }
}

2. 接口實現完整性

interface RequiredMethods {
  method1(): void
  method2(): string
}

// 錯誤:沒有實現所有接口方法
// class IncompleteClass implements RequiredMethods {
//   method1(): void { }
//   // 缺少method2
// }

// 正確:實現所有接口方法
class CompleteClass implements RequiredMethods {
  method1(): void { }
  method2(): string { return '' }
}

3. 訪問控制注意事項

class AccessExample {
  private secret: string = '機密信息'
  
  // 正確:通過公共方法訪問私有屬性
  getSecret(): string {
    return this.secret
  }
}

let example = new AccessExample()
// console.log(example.secret)  // 錯誤:secret是私有的
console.log(example.getSecret())  // 正確:通過公共方法訪問

性能優化建議

1. 避免過度使用繼承

  • 優先使用組合而非繼承
  • 保持繼承層次簡單(建議不超過3層)

2. 合理使用訪問控制

  • 將屬性設置為private,通過方法控制訪問
  • 使用protected允許子類擴展,同時限制外部訪問

3. 靜態成員的使用場景

  • 工具類方法適合作為靜態方法
  • 常量值適合作為靜態屬性
  • 避免在靜態方法中保存狀態

總結

通過本文的學習,您應該已經掌握了ArkTS中類和接口的核心概念:

  • 類定義:使用class關鍵字,包含屬性、構造器和方法
  • 接口:定義對象結構,確保實現一致性
  • 繼承:使用extends實現代碼複用
  • 訪問控制:public、private、protected控制可見性
  • 靜態成員:屬於類本身的屬性和方法
  • 抽象類:提供基礎框架,要求子類實現特定方法

這些面向對象編程的概念是構建複雜HarmonyOS應用的基礎。在實際開發中,合理運用這些特性可以提高代碼的可維護性、可擴展性和重用性。

下一步學習建議:

  • 深入學習ArkTS的泛型編程
  • 掌握裝飾器在HarmonyOS中的應用
  • 學習狀態管理和數據綁定的高級用法
  • 實踐更多HarmonyOS UI組件與ArkTS類的結合使用

繼續探索ArkTS的強大功能,構建出色的HarmonyOS應用!