“不可变”优先****
不可变(Immutable)指的是在变量赋值或对象创建结束之后,使用者就不能再改变它的值或状态。不可变意味着只读不写,因此不可变对象天然地具备线程安全的特性,即如无其它特殊限制的话可以在任何线程上自由调用。此外,相较于可变对象,不可变对象的访问没有副作用,因此在一些场合下也会让程序更易于了解,而且提供较高的安全性。
不可变通常可以分为两种,一种是不可变变量,不可变变量是指经初始化后其值就不可被修改的变量;另一种是不可变类型,不可变类型是指在构造完成后实际数据对象的内容无法被改变。
在仓颉中,let 定义的变量是不可变变量,而像 String、enum 等类型是不可变类型,这些都是不可变思想在仓颉中的应用。更多地使用不可变特性可以让程序更安全,也更利于理解和维护。
函数参数不可变****
在仓颉中,所有函数形参都是不可变的,这意味着我们无法对形参赋值,如果形参是值类型,也无法修改形参的成员。
| struct Point { var x: Int var y: Int init(x: Int, y: Int) { ... } ...} func f(a: Point) { // a 不可变 a = Point(0, 0) // error a.x = 2 // error} |
|---|
视频解析“KCKCJY”
模式匹配引入的新变量不可变****
在仓颉中,模式匹配支持变量绑定模式,我们可以将目标值解析到新绑定的变量中,但这个变量仍然是不可变的。这意味着我们无法对绑定的变量赋值,如果变量是值类型,也无法修改变量的成员。
| func f(a: ?Point) { match (a) { case Some(b) => //b 不可变 b = Point(0, 0) // error b.x = 2 // error case None => () }} |
|---|
闭包捕获可变变量不允许逃逸****
在仓颉中,闭包指的自包含的函数或 lambda,闭包可以从定义它的静态作用域中捕获变量,即使对闭包调用不在定义的作用域,仍可以访问其捕获的变量。
仓颉中允许闭包捕获可变变量,但不允许该闭包继续逃逸,这避免了对可变变量修改可能导致的意外行为。
| func f() { let a = 1 var b = 2 func g() { print(a) // ok print(b) // ok } return g // error, g 捕获了可变变量 b,g 不允许作为表达式使用。} |
|---|
默认封闭****
仓颉虽然支持了完整的面向对象范式,支持了类继承的特性,但仓颉并不鼓励滥用继承,尤其是默认可继承可覆盖。默认可继承语义会使得开发者设计的库无意间被使用者增加了抽象层次,提升了不必要的复杂性,从而引起一系列工程维护问题。
出于工程友好性的考虑,仓颉采取了默认封闭的设计选择,即类默认不可被继承,方法默认也不可被覆盖(override)。开发者需要主动考虑是否需要自己的类型提供子类的能力,通过这样的约束减少了滥用继承的现象。
类默认不可继承****
在仓颉中,开发者定义 class 时默认是不可继承的。如果希望该 class 有子类,必须显式使用 open、abstract、sealed 其中一个修饰(这些修饰符的语义有细微差别,但都允许 class 被继承)。
| class A {} class B <: A {} // error,A 不允许被继承 open class C {} class D <: C {} // ok |
|---|
成员方法默认不可覆盖****
在仓颉中,开发者定义成员方法默认是不可覆盖(override)的。这意味着即使该类拥有子类,子类也无法修改该成员方法。如果希望一个成员方法可以被覆盖,必须显式使用 open 修饰。
| open class A { func f() {} open func g() {}} class B <: A { override func f() {} // error,f 不允许被覆盖 override func g() {} // ok} |
|---|