仓颉编程语言作为一门现代化的多范式编程语言,提供了面向对象编程的核心特性,包括类(class)、接口(interface)和属性(property)等。这些特性共同构成了仓颉语言面向对象编程的基础设施,使开发者能够构建模块化、可复用且类型安全的代码。本文将详细介绍仓颉语言中这三个核心概念的定义、特性及使用方法。

一、类(Class)

1.1 类的基本概念

在仓颉语言中,**类(class)**是面向对象编程的基本构建块,用于定义对象的蓝图。类可以包含成员变量、成员属性、构造函数、成员函数等多种成员。与struct的主要区别在于:class是引用类型,struct是值类型;class之间可以继承,而struct之间不能继承。

类的基本定义语法如下:

 
class 类名 {
    // 成员变量
    // 构造函数
    // 成员函数
    // 成员属性
}

1.2 类的定义与成员

一个典型的类定义示例如下:

 
class Rectangle {
    // 成员变量
    let width: Int64
    let height: Int64

    // 构造函数
    public init(width: Int64, height: Int64) {
        this.width = width
        this.height = height
    }

    // 成员函数
    public func area(): Int64 {
        return width * height
    }
}

在这个例子中:

  • widthheight是成员变量
  • init是构造函数
  • area()是成员函数

1.3 构造函数

仓颉中的类可以有多个构造函数,用于以不同方式初始化对象。构造函数使用init关键字定义:

 
class Person {
    var name: String
    var age: Int64
    
    // 主构造函数
    public init(name: String, age: Int64) {
        this.name = name
        this.age = age
    }
    
    // 辅助构造函数
    public init(name: String) {
        this.init(name, 0)
    }
}

1.4 抽象类

使用abstract修饰的类为抽象类,与普通类不同的是,抽象类中可以声明抽象函数(没有函数体)。抽象类的子类必须实现这些抽象函数:

 
abstract class Shape {
    // 抽象函数
    public abstract func area(): Float64
}

class Circle <: Shape {
    var radius: Float64
    
    public override func area(): Float64 {
        return 3.14 * radius * radius
    }
}

1.5 This类型

在类内部,支持使用This类型占位符,代指当前类的类型。它只能作为实例成员函数的返回类型使用:

 
open class C1 {
    func f(): This {  // 返回类型为C1
        return this
    }
}

class C2 <: C1 {
    // 继承的f()函数现在返回类型为C2
}

二、接口(Interface)

2.1 接口的基本概念

**接口(interface)**是仓颉语言中定义抽象类型的方式,它可以包含成员函数、操作符重载函数和成员属性,要求实现类型必须拥有对应的成员实现。通过接口,可以对一系列类型约定共同的功能,达到对功能进行抽象的目的。

接口的定义语法如下:

 
interface 接口名 {
    // 抽象成员
}

2.2 接口的定义与实现

一个简单的接口定义示例:

 
interface Drawable {
    func draw(): Unit
}

类型可以通过<:语法实现接口:

 
class Circle <: Drawable {
    public func draw(): Unit {
        println("Drawing a circle")
    }
}

2.3 接口的成员

接口可以包含三种成员:

  • 成员函数
  • 操作符重载函数
  • 成员属性
 
interface Calculable {
    // 成员函数
    func calculate(): Int64
    
    // 操作符重载
    operator func +(other: Self): Self
    
    // 成员属性
    prop value: Int64
}

2.4 接口的默认实现

接口的成员可以提供默认实现:

 
interface Greetable {
    func greet() {
        println("Hello!")
    }
}

class Person <: Greetable {
    // 可以选择不实现greet(),使用默认实现
}

class CustomPerson <: Greetable {
    // 也可以选择提供自己的实现
    public func greet() {
        println("Hi there!")
    }
}

2.5 接口的多继承

一个类型可以实现多个接口,使用&分隔:

 
interface A { func a(): Unit }
interface B { func b(): Unit }

class C <: A & B {
    public func a(): Unit { ... }
    public func b(): Unit { ... }
}

接口也可以继承其他接口:

 
interface A { func a(): Unit }
interface B <: A { func b(): Unit }

// 实现B的类也需要实现A的方法
class C <: B {
    public func a(): Unit { ... }
    public func b(): Unit { ... }
}

三、属性(Property)

3.1 属性的基本概念

**属性(Properties)**提供了一个getter和一个可选的setter来间接获取和设置值。使用属性的时候与普通变量无异,但可以对内部实现进行封装,实现访问控制、数据监控等机制。

属性的基本语法:

 
class 类名 {
    [修饰符] prop 属性名: 类型 {
        get() { ... }
        set(value) { ... }
    }
}

3.2 属性的定义

一个简单的属性示例:

 
class Person {
    private var _age: Int64 = 0
    
    public mut prop age: Int64 {
        get() {
            println("Getting age")
            return _age
        }
        set(value) {
            println("Setting age")
            _age = value
        }
    }
}

3.3 属性的分类

属性分为两类:

  1. 只读属性:只有getter,没有setter
  2. 可读写属性:既有getter也有setter

只读属性定义:

 
class Circle {
    var radius: Float64
    
    // 只读属性
    public prop area: Float64 {
        get() {
            return 3.14 * radius * radius
        }
    }
}

可读写属性需要使用mut修饰:

 
class Temperature {
    private var _celsius: Float64 = 0.0
    
    public mut prop celsius: Float64 {
        get() { _celsius }
        set(v) { _celsius = v }
    }
    
    public mut prop fahrenheit: Float64 {
        get() { _celsius * 9/5 + 32 }
        set(v) { _celsius = (v - 32) * 5/9 }
    }
}

3.4 属性的使用场景

属性特别适合以下场景:

  1. 需要封装内部存储,对外提供统一访问接口
  2. 需要在获取或设置值时执行额外逻辑(如验证、通知等)
  3. 需要计算派生值
 
class BankAccount {
    private var _balance: Int64 = 0
    
    public mut prop balance: Int64 {
        get() { _balance }
        set(value) {
            if value < 0 {
                throw Exception("Balance cannot be negative")
            }
            _balance = value
        }
    }
    
    // 只读计算属性
    public prop isOverdrawn: Bool {
        get() { _balance < 0 }
    }
}

3.5 属性与字段的区别

虽然属性看起来像字段,但它们有重要区别:

  1. 属性是计算得到的,可以有复杂的获取和设置逻辑
  2. 属性可以只有getter(只读)或同时有getter和setter(可读写)
  3. 属性可以基于其他字段计算得到
 
class Person {
    // 字段
    private var firstName: String
    private var lastName: String
    
    // 计算属性
    public prop fullName: String {
        get() { "${firstName} ${lastName}" }
        set(value) {
            let parts = value.split(" ")
            firstName = parts
            lastName = parts
        }
    }
}

四、类、接口与属性的综合应用

4.1 使用接口定义契约

 
interface Shape {
    prop area: Float64 { get }
    func draw(): Unit
}

class Circle <: Shape {
    var radius: Float64
    
    public prop area: Float64 {
        get() { 3.14 * radius * radius }
    }
    
    public func draw(): Unit {
        println("Drawing circle with radius ${radius}")
    }
}

class Rectangle <: Shape {
    var width: Float64
    var height: Float64
    
    public prop area: Float64 {
        get() { width * height }
    }
    
    public func draw(): Unit {
        println("Drawing rectangle ${width}x${height}")
    }
}

4.2 使用属性封装状态

 
class Thermostat {
    private var _temperature: Float64 = 20.0
    private var _mode: String = "cooling"
    
    public mut prop temperature: Float64 {
        get() { _temperature }
        set(value) {
            if value < 10 || value > 40 {
                throw Exception("Temperature out of range")
            }
            _temperature = value
        }
    }
    
    public mut prop mode: String {
        get() { _mode }
        set(value) {
            if value != "cooling" && value != "heating" {
                throw Exception("Invalid mode")
            }
            _mode = value
        }
    }
}

4.3 实现多态行为

 
interface Logger {
    func log(message: String): Unit
}

class ConsoleLogger <: Logger {
    public func log(message: String): Unit {
        println("[Console] ${message}")
    }
}

class FileLogger <: Logger {
    var filePath: String
    
    public func log(message: String): Unit {
        // 实现文件写入逻辑
    }
}

class App {
    private var _logger: Logger
    
    public init(logger: Logger) {
        _logger = logger
    }
    
    public func run(): Unit {
        _logger.log("Application started")
        // 应用逻辑
    }
}

五、最佳实践与注意事项

5.1 类的设计原则

  1. 单一职责原则:一个类应该只有一个引起它变化的原因
  2. 开闭原则:类应该对扩展开放,对修改关闭
  3. 依赖倒置原则:依赖抽象而不是具体实现

5.2 接口的设计建议

  1. 接口应该小而专注(ISP原则)
  2. 优先使用组合而不是继承
  3. 考虑为常用操作提供默认实现

5.3 属性的使用技巧

  1. 保持属性操作轻量级,复杂逻辑应放在方法中
  2. 考虑线程安全性,特别是在多线程环境中
  3. 避免在属性getter/setter中抛出异常
  4. 避免属性依赖其他可能为null的对象

5.4 常见陷阱

  1. 避免属性递归调用:
 
public mut prop value: Int64 {
    get() { value }  // 无限递归!
    set(v) { value = v }  // 无限递归!
}
  1. 注意值类型和引用类型的区别:
 
struct Point { var x, y: Int64 }
class Line { var start, end: Point }

var line = Line()
line.start.x = 10  // 这不会修改line的start,因为Point是值类型
  1. 谨慎使用可变属性,特别是在多线程环境中

六、总结

仓颉语言中的类、接口和属性构成了其面向对象编程的核心。类提供了对象的蓝图和实现,接口定义了类型之间的契约,而属性则提供了灵活的数据访问方式。通过合理使用这些特性,开发者可以构建出模块化、可扩展且易于维护的代码。

关键要点:

  1. 类是引用类型,支持继承和多态
  2. 接口定义抽象行为,一个类型可以实现多个接口
  3. 属性封装了状态访问,可以添加额外逻辑
  4. 组合优于继承,面向接口编程
  5. 遵循SOLID原则设计类和接口

通过掌握这些概念和技巧,开发者可以充分利用仓颉语言的面向对象特性,构建出高质量的软件系统。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐