继承接口与抽象类

类不仅可以继承另一个类,还可以继承接口和抽象类。接口和抽象类都不能直接实例化,其中的成员定义必须由继承它的子类来实现。接口用于描述对象类型的结构,其中的成员只拥有类型定义而没有具体实现。在抽象类中指定某些成员只有定义,具体逻辑需要由继承它的子类来实现,而指定另一些成员拥有具体实现,子类继承后可以共用。

继承接口

类继承接口需要使用 implements 关键字,其语法如下。

class 类名称 implements 接口名称1, 接口名称2,...,接口名称n {
    //类的成员以及接口的成员
}

例如,以下代码声明了一个 IUser 接口,它拥有两个待实现成员——name 字段和 login() 方法。GuestUser 继承了该接口,并实现了这两个接口成员,NormalUser 也继承了 IUser 接口,但区别在于,NormalUsername 字段以存取器的方式实现。

interface IUser {
    name: string,
    login: (pwd: string) => boolean;
}

class GuestUser implements IUser {
    name: string;
    login(pwd: string) {
        return true;
    }
    authority: string = "guest";
}

class NormalUser implements IUser {
    _name: string;
    get name() {
        return this.name;
    }
    set name(value: string) {
        this._name = value;
    }
    login(pwd: string) {
        return pwd == "123456";
    }
    authority: string = "normal";
}

继承接口后,接口中的成员在类中必须拥有具体实现代码,否则会引起编译错误。例如,以下代码中的 AdminUser 继承了 IUser 接口,但没有实现任何成员,因此引起了编译错误。

//类"AdminUser"错误实现接口"IUser"
//类型"AdminUser"缺少类型"IUser"中的属性name和login
class AdminUser implements IUser {

}

和继承类不同,接口支持多继承,这意味着一个类可以同时继承并实现多个接口,示例代码如下。

interface Checkable {
    check: () => void;
}

interface Clickable {
    click: (x: number, y: number) => void;
}

class Checkbox implements Checkable, Clickable {
    check() {
        console.log("this checkbox has been checked!")
    }
    click(x: number, y: number) {
        console.log(`click location ${x},${y}`)
    }
}

接口中可以指定可选成员,类在继承该接口后可以不实现已指定的可选成员,但这也意味着可选成员并不属于类的成员,因此不可以使用。例如,在以下代码中,接口 A 中定义了一个可选成员 y,类 B 继承了接口 A,但只实现了成员 x,因此该类只有成员 x,并没有成员 y。如果尝试操作类B的实例对象的 y 成员,将会引起编译错误。

interface A {
    x: string;
    y?: number;
}

class B implements A {
    x: string;
}

let b:B=new B();
b.x = "abc";
//编译错误:类型"B"上不存在属性"y"。ts(2339)
b.y = 1;

继承抽象类

和接口不同,抽象类既可以拥有成员定义,又可以拥有具体实现。在 TypeScript 中,用 abstract 关键字来声明抽象类。那些仅拥有定义而没有具体实现的成员也需要加上 abstract 前缀,表示它是抽象成员。抽象类不能实例化,只能实例化继承了抽象类的普通子类。

抽象类的声明语法如下。

abstract class 类名 {
    //定义抽象成员
    abstract 属性名称1: 属性类型;
    abstract 属性名称2: 属性类型;
    abstract 方法名称1(参数列表...): 返回值类型 { /*方法代码块*/ }
    abstract 方法名称2(参数列表...): 返回值类型 { /*方法代码块*/ }
    ...
    //抽象类中也可以定义拥有具体实现的成员
    属性名称3: 属性类型;
    属性名称4: 属性类型;
    方法名称3(参数列表...): 返回值类型 { /*方法代码块*/ }
    方法名称4(参数列表...): 返回值类型 { /*方法代码块*/ }
}

例如,以下代码声明了 Person 抽象类,它拥有一个抽象属性 age、一个抽象只读存取器 name 和一个抽象方法 myLocation()。同时,它还定义了一个拥有具体实现的方法 selfIntroduction(),在该方法中使用了各个成员。

abstract class Person {
    abstract age: number;
    abstract get name();
    abstract myLocation();
    selfIntroduction() {
        console.log(`My name is ${this.name}, I'm ${this.age} years old. I live in
        ${this.myLocation()}`);
    }
}

下面编写代码,继承 Person 抽象类。以下代码声明了一个 ChinesePerson 类,它继承了 Person 抽象类,并实现了它的 3 个成员——属性 age、只读存取器 namemyLocation() 方法,然后实例化 ChinesePerson 类的对象,调用 selfIntroduction() 方法,最终顺利输出各个成员的值。

class ChinesePerson extends Person {
    //实现抽象类Person中的age属性
    age: number;
    familyName: string;
    givenName: string;
    province: string;
    city: string;
    constructor(familyName: string, givenName: string, age: number, province: string,
    city: string) {
        super();
        this.familyName = familyName;
        this.givenName = givenName;
        this.age = age;
        this.province = province;
        this.city = city;
    }
    //实现抽象类Person中的name存取器
    get name() {
        return this.familyName + this.givenName;
    }

    myLocation() {
        return `${this.city}, ${this.province}`;
    }
}

let person1: ChinesePerson = new ChinesePerson("Zhou", "Jun", 28, "ChengDu", "SiChuan");
person1.selfIntroduction(); //输出
"My name is ZhouJun, I'm 28 years old. I live in SiChuan, ChengDu"

使用抽象类的注意事项

注意,抽象类本身不能实例化,必须由普通类继承该抽象类,然后才可以实例化普通类的对象。如果直接实例化抽象类,将引起编译错误。示例代码如下。

//编译错误:无法创建抽象类的实例。ts(2511)
let person1: Person = new Person();

抽象类和普通类的继承不同,继承抽象类后,所有的抽象成员必须在子类中拥有具体实现,否则也会引起编译错误。示例代码如下。

//编译错误:非抽象类"AmericanPerson"不会实现继承自"Person"类的抽象成员"age"、"myLocation"、
//"name"。ts(2515)
class AmericanPerson extends Person { }

抽象成员(有 abstract 前缀的成员)不可以拥有具体实现,否则也会引起编译错误。示例代码如下。

abstract class A {
    //编译错误:属性"a"不能具有初始化表达式,因为它标记为摘要。ts(1267)
    abstract a: number = 10;
    //编译错误:抽象访问器不能有实现。ts(1318)
    abstract set b(value: number) { this.a = value; }
    //编译错误:抽象访问器不能有实现。ts(1318)
    abstract get b() { return 1; }
    //编译错误:方法"c"不能具有实现,因为它标记为抽象。ts(1245)
    abstract c() { }

    d: number = 20;
    set e(value: number) { this.d = value; }
    get f() { return 1; }
    g() { }
}