使用新版本 (2024-07-19 16:10发布的)
1、扩展概述
扩展可以为在当前 package 可见的类型
(除函数、元组、接口
)添加新功能。
当我们不能破坏原有类型的封装性,但希望添加额外的功能时
,可以使用扩展。
可以添加的功能包括:
- 添加成员函数
- 添加操作符重载函数
- 添加成员属性
- 实现接口
扩展虽然可以添加额外的功能,但不能变更原有类型的封装性,因此扩展不支持以下功能
:
- 扩展不能增加成员变量。
- 扩展的函数和属性必须拥有实现。
- 扩展的函数和属性不能使用 open、override、 redef修饰。
- 扩展不能访问原类型 private 的成员。
根据扩展有没有实现新的接口
,扩展可以分为 直接扩展
和 接口扩展
两种用法,直接扩展即不包含额外接口的扩展;接口扩展即包含接口的扩展,接口扩展可以用来为现有的类型添加新功能并实现接口,增强抽象灵活性。
2、直接扩展
一个简单的扩展语法结构示例如下:
extend String {
public func printSize() {
println("the size is ${this.size}")
}
}
如上例所示,扩展使用 extend 关键字声明
,其后跟着被扩展的类型 String
和扩展的功能
。
当为 String 扩展了 printSize 函数之后,我们就能在当前 package 内对 String 的实例访问该函数,就像是 String 本身具备该函数。
main() {
let a = "123"
a.printSize() // the size is 3
}
被扩展类型是泛型类型时
,有两种扩展语法可以对泛型类型扩展功能。
一种是针对特定泛型实例化类型进行扩展
,关键字 extend 后允许带一个任意实例化完全的泛型类型。为这些类型增加的功能只有在类型完全匹配时才能使用,且泛型类型的类型实参必须符合泛型类型定义处的约束要求。
例如下面所示的 Foo<T>
。
class Foo<T> where T <: ToString {}
extend Foo<Int64> {} // Ok
class Bar {}
extend Foo<Bar> {} // Error
另一种是在 extend 后面引入泛型形参的泛型扩展
。泛型扩展可以用来扩展未实例化
或未完全实例化
的泛型类型。在 extend 后声明的泛型形参必须被直接或间接使用在被扩展的泛型类型上
。为这些类型增加的功能只有在类型和约束完全匹配时才能使用。
例如下面所示的 MyList<T>
。
class MyList<T> {
public let data: Array<T> = Array<T>()
}
extend<T> MyList<T> {} // OK
extend<R> MyList<R> {} // OK
extend<T, R> MyList<(T, R)> {} // OK
extend MyList {} // Error
extend<T, R> MyList<T> {} // Error
extend<T, R> MyList<T, R> {} // Error
对于泛型类型的扩展
,我们可以在其中声明额外的泛型约束
,来实现一些有限情况下才能使用的函数
。
例如我们可以定义一个叫 Pair 的类型,这个类型可以让我们方便的存储两个元素(类似于 Tuple)。
我们希望 Pair 类型可以容纳任何类型,因此两个泛型变元不应该有任何约束,这样才能保证 Pair 能容纳所有类型。
但同时我们又希望当两个元素可以判等的时候,让 Pair 也可以判等,这时就可以用扩展来实现这个功能。
如下面的代码所示,我们使用扩展语法,约束了 T1 和 T2 在支持 equals 的情况下,Pair 也可以实现 equals 函数。
class Pair<T1, T2> {
var first: T1
var second: T2
public init(a: T1, b: T2) {
first = a
second = b
}
}
interface Eq<T> {
func equals(other: T): Bool
}
extend<T1, T2> Pair<T1, T2> where T1 <: Eq<T1>, T2 <: Eq<T2> {
public func equals(other: Pair<T1, T2>) {
first.equals(other.first) && second.equals(other.second)
}
}
class Foo <: Eq<Foo> {
public func equals(other: Foo): Bool {
true
}
}
main() {
let a = Pair(Foo(), Foo())
let b = Pair(Foo(), Foo())
println(a.equals(b)) // true
}
3、接口扩展
例如下面的例子,类型 Array 本身没有实现接口 PrintSizeable,但我们可以通过扩展的方式为 Array 增加额外的成员函数 printSize,并实现 PrintSizeable。
interface PrintSizeable {
func printSize(): Unit
}
extend<T> Array<T> <: PrintSizeable {
public func printSize() {
println("The size is ${this.size}")
}
}
当使用扩展为 Array 实现 PrintSizeable 之后,就相当于在 Array 定义时实现接口 PrintSizeable。
因此我们可以将 Array 作为 PrintSizeable 的实现类型来使用了,如以下代码所示。
main() {
let a: PrintSizeable = Array<Int64>()
a.printSize() // 0
}
我们可以在同一个扩展内同时实现多个接口
,多个接口之间使用 & 分开
,接口的顺序没有先后关系
。
如下面代码所示,我们可以在扩展中为 Foo 同时实现 I1、I2、I3。
interface I1 {
func f1(): Unit
}
interface I2 {
func f2(): Unit
}
interface I3 {
func f3(): Unit
}
class Foo {}
extend Foo <: I1 & I2 & I3 {
public func f1(): Unit {}
public func f2(): Unit {}
public func f3(): Unit {}
}
我们也可以在接口扩展中声明额外的泛型约束
,来实现一些特定约束下才能满足的接口。
例如我们可以让上面的 Pair 类型实现 Eq 接口,这样 Pair 自己也能成为一个符合 Eq 约束的类型,如下代码所示。
class Pair<T1, T2> {
var first: T1
var second: T2
public init(a: T1, b: T2) {
first = a
second = b
}
}
interface Eq<T> {
func equals(other: T): Bool
}
extend<T1, T2> Pair<T1, T2> <: Eq<Pair<T1, T2>> where T1 <: Eq<T1>, T2 <: Eq<T2> {
public func equals(other: Pair<T1, T2>) {
first.equals(other.first) && second.equals(other.second)
}
}
class Foo <: Eq<Foo> {
public func equals(other: Foo): Bool {
true
}
}
main() {
let a = Pair(Foo(), Foo())
let b = Pair(Foo(), Foo())
println(a.equals(b)) // true
}
如果被扩展的类型已经包含接口要求的函数或属性,那么我们在扩展中不需要并且也不能重新实现这些函数或属性。
例如下面的例子,我们定义了一个新接口 Sizeable,目的是获得某个类型的 size,而我们已经知道 Array 中包含了这个函数,因此我们就可以通过扩展让 Array 实现 Sizeable,而不需要添加额外的函数。
interface Sizeable {
prop size: Int64
}
extend<T> Array<T> <: Sizeable {}
main() {
let a: Sizeable = Array<Int64>()
println(a.size) // 0
}
4、访问规则
4.1 扩展的修饰符
扩展本身不能使用修饰符修饰。
例如,下面的例子中对 A 的直接扩展前使用了 public 修饰,将编译报错。
public class A {}
public extend A {} // Error, expected no modifier before extend
扩展成员可使用的修饰符有:static
、public
、protected
(仅限于被扩展类型是 class 类型)、private
、mut
。
- 使用 private 修饰的成员只能在本扩展内使用,外部不可见。
- 使用 protected 修饰的成员除了能在本包内被访问,对包外的当前 class 子类也可以访问。
- 没有使用 private,protected 或 public 修饰的成员只能在本包内使用。
- 使用 static 修饰的成员,只能通过类型名访问,不能通过实例对象访问。
- 对 struct 类型的扩展可以定义 mut 函数。
package p1
public open class A {}
extend A {
public func f1() {}
protected func f2() {}
private func f3() {}
static func f4() {}
}
main() {
A.f4()
var a = A()
a.f1()
a.f2()
}
扩展内的成员定义不支持使用 open、override、redef 修饰。
class Foo {
public open func f() {}
static func h() {}
}
extend Foo {
public override func f() {} // Error
public open func g() {} // Error
redef static func h() {} // Error
}
4.2 扩展的孤儿规则
为一个其它 package 的类型实现另一个 package 的接口
,可能造成理解上的困扰。
为了防止一个类型被意外实现不合适的接口,仓颉不允许定义孤儿扩展
,指的是既不与接口(包含接口继承链上的所有接口)定义在同一个包中,也不与被扩展类型定义在同一个包中的接口扩展
。
如下代码所示,我们不能在 package c 中,为 package a 里的 Foo 实现 package b 里的 Bar。
我们只能在 package a 或者在 package b 中为 Foo 实现 Bar。
// package a
public class Foo {}
// package b
public interface Bar {}
// package c
import a.Foo
import b.Bar
extend Foo <: Bar {} // Error
4.3 扩展的访问和遮盖
扩展的实例成员与类型定义处一样可以使用 this,this 的功能保持一致。同样也可以省略 this 访问成员**。扩展的实例成员不能使用 super。**
class A {
var v = 0
}
extend A {
func f() {
print(this.v) // Ok
print(v) // Ok
}
}
扩展不能访问被扩展类型的 private 修饰的成员(意味着非 private 修饰的成员均能被访问
)。
class A {
private var v1 = 0
protected var v2 = 0
}
extend A {
func f() {
print(v1) // Error
print(v2) // Ok
}
}
扩展不能遮盖被扩展类型的任何成员。
class A {
func f() {}
}
extend A {
func f() {} // Error
}
扩展也不允许遮盖其它扩展增加的任何成员。
class A {}
extend A {
func f() {}
}
extend A {
func f() {} // Error
}
在同一个 package 内对同一类型可以扩展多次。
在扩展中可以直接调用(不加任何前缀修饰)其它对同一类型的扩展中的非 private 修饰的函数
。
class Foo {}
extend Foo { // OK
private func f() {}
func g() {}
}
extend Foo { // OK
func h() {
g() // OK
f() // Error
}
}
扩展泛型类型时,可以使用额外的泛型约束。泛型类型的任意两个扩展之间的可见性规则如下:
- 如果两个扩展的约束相同,则两个扩展相互可见,即两个扩展内可以直接使用对方内的函数或属性;
- 如果两个扩展的约束不同,且两个扩展的约束有包含关系,约束更宽松的扩展对约束更严格的扩展可见,反之,不可见;
- 当两个扩展的约束不同时,且两个约束不存在包含关系,则两个扩展均互相不可见。
示例:假设对同一个类型 E 的两个扩展分别为扩展 1 和扩展 2 ,X 的约束在扩展 1 中比扩展 2 中更严格,那么扩展 1 中的函数和属性对扩展 2 均不可见,反之,扩展 2 中的函数和属性对扩展 1 可见。
open class A {}
class B <: A {}
class E<X> {}
interface I1 {
func f1(): Unit
}
interface I2 {
func f2(): Unit
}
extend<X> E<X> <: I1 where X <: B { // extension 1
public func f1(): Unit {
f2() // OK
}
}
extend<X> E<X> <: I2 where X <: A { // extension 2
public func f2(): Unit {
f1() // Error
}
}
4.4 扩展的导入导出
扩展也是可以被导入和导出的,但是扩展本身不能使用 public 修饰,扩展的导出有一套特殊的规则。
对于直接扩展
,只有当扩展与被扩展的类型在同一个 package
,并且被扩展的类型
和扩展中添加的成员
都使用 public 或 protected 修饰
时,扩展的功能才会被导出。
除此以外的直接扩展均不能被导出,只能在当前 package 使用。
如以下代码所示,Foo 是使用 public 修饰的类型,并且 f 与 Foo 在同一个 package 内,因此 f 会跟随 Foo 一起被导出。而 g 和 Foo 不在同一个 package,因此 g 不会被导出。
// package a
public class Foo {}
extend Foo {
public func f() {}
}
// package b
import a.*
extend Foo {
public func g() {}
}
// package c
import a.*
import b.*
main() {
let a = Foo()
a.f() // OK
a.g() // Error
}
对于接口扩展
则分为两种情况:
- 如果接口扩展和被扩展类型在同一个 package,但接口是来自导入的,只有当被扩展类型使用 public 修饰时,扩展的功能才会被导出。
- 如果接口扩展与接口在同一个 package,则只有当接口是使用 public 修饰时,扩展的功能才会被导出。
如下代码所示,Foo 和 I 都使用了 public 修饰,因此对 Foo 的扩展就可以被导出。
// package a
public class Foo {}
public interface I {
func g(): Unit
}
extend Foo <: I {
public func g(): Unit {}
}
// package b
import a.*
main() {
let a: I = Foo()
a.g()
}
与扩展的导出类似,扩展的导入也不需要显式地用 import 导入,扩展的导入只需要导入被扩展的类型和接口
,就可以导入可访问的所有扩展。
如下面的代码所示,在 package b 中,只需要导入 Foo 就可以使用 Foo 对应的扩展中的函数 f。
而对于接口扩展,需要同时导入被扩展的类型和扩展的接口才能使用,因此在 package c 中,需要同时导入 Foo 和 I 才能使用对应扩展中的函数 g。
// package a
public class Foo {}
extend Foo {
public func f() {}
}
// package b
import a.Foo
public interface I {
func g(): Unit
}
extend Foo <: I {
public func g() {
this.f() // OK
}
}
// package c
import a.Foo
import b.I
func test() {
let a = Foo()
a.f() // OK
a.g() // OK
}