当我们需要在当前 package 中可见的类型(除函数、元组、接口)添加新功能,又不想或不能直接修改源文件时,这时就可以用到扩展功能。扩展可以不破坏类型的封装性,还能增加额外的功能。
可以添加的功能包括:成员函数、操作符重载函数、成员属性、实现接口。
扩展不支持以下功能:
1.扩展不能增加成员变量。
2.扩展的函数和属性必须拥有实现。
3.扩展的函数和属性不能使用 open、override、 redef修饰。
4.扩展不能访问被扩展类型中 private 修饰的成员。
根据扩展有没有实现新的接口,扩展可以分为直接扩展和接口扩展两种用法。
直接扩展
直接扩展即不包含额外接口的扩展。
package cangjiestudy
//定义一个学生类
class Student{
protected var age:Int64=0
public Student(var name:String){}
func run(){
println('${name} 跑步')
}
}
//扩展 Student
extend Student{
//扩展成员属性 修改非private修饰的变量age
public mut prop a:Int64{
get(){
this.age
}
set(value){
age = value
}
}
//添加成员函数
func swimming(){
println('${name} 游泳')
}
//扩展操作符重载函数
public operator func == (s:Student):Bool{
return this.age==s.age&&this.name==s.name
}
}
main() {
var stu = Student('小明')
var stu2 = Student('小明')
stu.a = 18
println(stu.a) //18
stu.run() //小明 跑步
stu.swimming() //小明 游泳
println(stu==stu2) //false
stu2.a = 18
println(stu==stu2) //true
}
接口扩展
可以在同一个扩展内同时实现多个接口,多个接口之间使用 & 分开,接口的顺序没有先后关系。
interface I1{
func run():Unit
}
interface I2{
func swimming():Unit
}
interface I3{
func eat():Unit
}
class People {
public People(var name:String){}
public func study(){
println('学习')
}
}
// 通过实现接口 给People 扩展了3个方法
extend People <: I1&I2&I3{
public func run(){
println('跑步')
}
public func swimming(){
println('游泳')
}
public func eat(){
println('吃饭')
}
}
main() {
var p = People('张三')
p.study()
p.eat()
}
可以在接口扩展中声明额外的泛型约束,来实现一些特定约束下才能满足的接口。
class Pair<T1, T2> {
public Pair(var first: T1, var second: T2) {}
}
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
}
如果被扩展的类型已经包含接口要求的函数或属性,在扩展中不能重新实现这些函数或属性。
访问规则
扩展的修饰符
扩展本身不能使用修饰符修饰。
扩展成员可使用的修饰符有:
private:只能在本扩展内使用,外部不可见。
internal:可以在当前包及子包(包括子包的子包)内使用,这是默认行为。
protected:在本模块内可以被访问(受导出规则限制)。当被扩展类型是 class 时,该 class 的子类定义体也能访问。
static:只能通过类型名访问,不能通过实例对象访问。
struct:可以定义 mut 函数。
扩展的孤儿规则
被扩展类、扩展接口、扩展类必须在被扩展类或扩展接口所在的包中。不能在第三个包中实现。
扩展的访问和遮盖
1.扩展的实例成员与类型定义处一样可以使用 this。
2.扩展不能访问被扩展类型中 private 修饰的成员。
3.扩展不能遮盖被扩展类型的任何成员。
4.在同一个包内,对同一类型可以扩展多次,并且在扩展中可以直接调用被扩展类型的其他扩展中非 private 修饰的函数。
class A {
var v = 0
private var v1 = 0
func a() {}
}
extend A {
func f() {
print(this.v) // Ok 扩展的实例成员与类型定义处一样可以使用 this
print(v) // Ok
print(v1) // Error 扩展不能访问被扩展类型中 private 修饰的成员。
}
func a() {} //Error 扩展不能遮盖被扩展类型的任何成员。
}
extend A{
func B(){
f() // OK 在同一个包内,对同一类型可以扩展多次,并且在扩展中可以直接调用被扩展类型的其他扩展中非 private 修饰的函数。
}
}
扩展的导入导出
直接扩展
当扩展与被扩展的类型在同一个包中,扩展是否导出,由被扩展类型与泛型约束(如果有)的访问修饰符同时决定,当所有的泛型约束都是导出类型时,该扩展将被导出。当扩展与被扩展类型不在同一个包中时,该扩展不会导出。
接口扩展
1.当接口扩展与被扩展类型在相同的 package 时,扩展会与被扩展类型以及泛型约束(如果有)一起被导出,不受接口类型的访问级别影响,包外不需要导入接口类型也能访问该扩展的成员。
2.当接口扩展与被扩展类型在不同的 package 时,接口扩展是否导出由接口类型以及泛型约束(如果有)里用到的类型中最小的访问级别决定。其他 package 必须导入被扩展类型、相应的接口以及约束用到的类型(如果有),才能访问对应接口包含的扩展成员。
扩展的导入
不需要显式地用 import 导入,扩展的导入只需要导入被扩展的类型、接口和泛型约束,就可以导入可访问的所有扩展。
// package a
package a
public class Foo {}
extend Foo {
public func f() {}
}
// package b
package b
import a.Foo
public interface I {
func g(): Unit
}
extend Foo <: I {
public func g() {
this.f() // OK
}
}
// package c
package c
import a.Foo //只需导入被扩展的类
import b.I //和扩展的接口 即可调用扩展的方法
func test() {
let a = Foo()
a.f() // OK
a.g() // OK
}