目录

一、不可变优先:让程序更安全可靠

1. 不可变变量:用let锁定值

2. 不可变类型:对象内部状态锁定

3. 函数参数:拒绝修改的 “只读门票”

4. 模式匹配:解包后仍是只读

5. 闭包陷阱:可变变量禁止 “逃逸”

二、默认封闭:拒绝滥用继承的 “防护墙”

1. 类默认不可继承:禁止 “子类化”

2. 方法默认不可覆盖:禁止 “重写偷袭”

3. 抽象类:强制子类 “填空”

三、为什么这样设计?—— 安全与工程的平衡

四、初学者实践建议

五、常见错误与解决

总结


一、不可变优先:让程序更安全可靠

在编程中,不可变(Immutable) 就像 “一次性印章”—— 一旦创建就无法修改。仓颉通过 “不可变优先” 设计,从根源上减少错误,让代码更安全、更易理解。

1. 不可变变量:用let锁定值
  • 语法:用let声明的变量是不可变变量,初始化后不能再修改。
    let age = 18       // 正确:age 是不可变变量,值为18
    age = 20           // ❌ 错误:不可变变量不能重新赋值
    
    • 类比:像考试卷上的答案,写完就不能改了。
2. 不可变类型:对象内部状态锁定
  • 什么是不可变类型?
    对象创建后,内部字段不能被修改。仓颉的Stringenumstruct默认都是不可变的。
    struct Point {
        let x: Int  // ❌ 用let声明字段,不可变
        let y: Int
    }
    
    let p = Point(x: 3, y: 5)
    p.x = 10       // ❌ 错误:不可变类型的字段不能修改
    
    • 为什么安全?
      不可变对象在多线程中不会被意外修改,避免 “数据打架”(线程安全)。
3. 函数参数:拒绝修改的 “只读门票”
  • 在仓颉中,所有函数参数都是不可变的,不能修改也不能重新赋值。
    struct MutablePoint {
        var x: Int  // 字段可变(用var)
        var y: Int
    }
    
    func changePoint(point: MutablePoint) {
        point.x = 99   // ❌ 错误:参数不可变,不能修改字段
        point = MutablePoint(x: 0, y: 0)  // ❌ 错误:不能重新赋值
    }
    
    • 好处:防止函数内部误改外部数据,保持函数 “纯净”(无副作用)。
4. 模式匹配:解包后仍是只读
  • 模式匹配(如match语句)绑定的变量默认不可变。
    enum Option<T> {
        Some(T)
        None
    }
    
    func processOption(opt: Option<Int>) {
        match opt {
            case Some(num) => {  // num 是不可变变量
                num = 0   // ❌ 错误:模式匹配绑定的变量不可变
            }
            case None => ()
        }
    }
    
    • 例外:极少场景需要可变,用var显式声明(不推荐)。
5. 闭包陷阱:可变变量禁止 “逃逸”
  • 闭包(Lambda)可以捕获外部变量,但如果捕获了可变变量(var),闭包不能被返回(逃逸)
    func makeClosure() {
        var count = 0  // 可变变量
    
        // 闭包捕获了可变变量 count
        let closure = {
            count += 1  // 允许在闭包内修改
        }
    
        return closure  // ❌ 错误:闭包捕获可变变量,禁止逃逸
    }
    
    • 为什么?
      若闭包被返回,可能在外部作用域销毁后继续修改变量,导致内存错误。
二、默认封闭:拒绝滥用继承的 “防护墙”

在面向对象编程中,继承是把双刃剑。仓颉通过 “默认封闭” 设计,强制开发者谨慎使用继承,避免代码复杂度失控。

1. 类默认不可继承:禁止 “子类化”
  • class定义的类默认不能被继承,必须用open/abstract/sealed显式允许。
    class Animal {
        func speak() { println("Animal sound") }
    }
    
    class Dog : Animal {}  // ❌ 错误:Animal 默认不可继承
    
    // 允许继承:用open修饰
    open class Vehicle {
        func start() { println("Vehicle started") }
    }
    
    class Car : Vehicle {}  // ✅ 正确:Vehicle 是open类
    
    • 修饰符区别
      • open:允许继承,子类可覆盖方法。
      • abstract:抽象类,必须被继承,不能实例化。
      • sealed:允许继承,但仅限同一模块内(限制继承范围)。
2. 方法默认不可覆盖:禁止 “重写偷袭”
  • 类的方法默认不能被子类修改(覆盖),必须用open显式允许。
    open class Shape {
        func area() -> Double { return 0.0 }  // 默认不可覆盖
        open func draw() { println("Drawing shape") }  // 允许覆盖
    }
    
    class Circle : Shape {
        override func area() -> Double {  // ❌ 错误:area() 不可覆盖
            return 3.14 * radius * radius
        }
    
        override func draw() {  // ✅ 正确:draw() 是open方法
            println("Drawing circle")
        }
    }
    
    • 好处:防止子类意外破坏父类核心逻辑,如 “鸟类” 被继承后重写 “飞行” 方法为 “不会飞”,导致逻辑混乱。
3. 抽象类:强制子类 “填空”
  • 抽象类是 “不完整的类”,必须被子类继承并实现抽象方法。
    abstract class Animal {
        abstract func speak() -> String  // 抽象方法,无实现
        func eat() { println("Eating...") }  // 普通方法,有实现
    }
    
    class Dog : Animal {
        override func speak() -> String {  // 必须实现抽象方法
            return "Woof!"
        }
    }
    
    let dog = Dog()  // 正确:子类实现了抽象方法
    let animal: Animal = Dog()  // 多态:父类引用指向子类对象
    • 注意:抽象类不能直接创建对象(let animal = Animal() ❌ 错误)。
三、为什么这样设计?—— 安全与工程的平衡
特性 传统语言(如 Java) 仓颉 优势
变量可变性 final 关键字强制不可变 默认let不可变 减少 90% 可变变量误用风险
类继承 默认可继承 默认不可继承 防止 “类爆炸”,代码层次更清晰
方法覆盖 默认可覆盖 默认不可覆盖 防止子类 “偷改” 父类核心逻辑
闭包捕获 可变变量可逃逸 禁止可变变量逃逸 避免内存泄漏和竞态条件
四、初学者实践建议
  1. 优先用letstruct

    • 除非必须修改,否则不用varclass
    // 推荐:不可变结构体
    struct User {
        let id: Int
        let name: String
    }
    
  2. 继承只用在明确需要多态时

    • 例如:图形类(Shape)有Circle/Rectangle子类,需统一计算面积。
  3. 闭包中避免捕获可变变量

    • 用不可变变量(let)替代,或限制闭包作用域。
  4. 组合优于继承

    • 用 “拼装” 组件代替继承,如UserService组合Logger而非继承。
五、常见错误与解决
错误信息 原因 解决方法
Cannot assign to let variable 尝试修改let变量 var声明变量
Class is not open for inheritance 继承了默认不可继承的类 给父类添加open修饰符
Overriding non-open function 重写了不可覆盖的方法 给父类方法添加open修饰符
Escaping closure captures mutable variable 闭包捕获可变变量并逃逸 let声明不可变变量,或不返回闭包

总结

  • 不可变优先:从变量、类型到函数参数,默认禁止修改,减少副作用和线程安全问题。
  • 默认封闭:类和方法默认不可继承 / 覆盖,强制开发者谨慎设计继承关系。
Logo

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

更多推荐