仓颉编程入门:扩展

当我们需要在当前 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
}


©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 扩展概述 扩展可以为在当前 package 可见的类型(除函数、元组、接口)添加新功能。 当不能破坏被扩展类型的封...
    松哥888阅读 936评论 0 0
  • 面向对象编程的四大核心特性:封装、继承、多态、抽象,相信已经深入大多开发者的骨髓。仓颉中同样支持使用 class ...
    auhgnixgnahz阅读 20评论 0 0
  • 函数是一个参数化的代码块,在调用函数时,这些代码块实现特定功能并可以被求值,结合函数参数实现特定范围的代码复用 定...
    auhgnixgnahz阅读 20评论 0 0
  • 在仓颉编程语言中,泛型指的是参数化类型,参数化类型是一个在声明时未知并且需要在使用时指定的类型。function、...
    auhgnixgnahz阅读 21评论 0 0
  • 包的概述 在仓颉编程语言中,包是编译的最小单元,每个包可以单独输出 AST 文件、静态库文件、动态库文件等产物。每...
    松哥888阅读 1,098评论 0 0