继承接口与抽象类
类不仅可以继承另一个类,还可以继承接口和抽象类。接口和抽象类都不能直接实例化,其中的成员定义必须由继承它的子类来实现。接口用于描述对象类型的结构,其中的成员只拥有类型定义而没有具体实现。在抽象类中指定某些成员只有定义,具体逻辑需要由继承它的子类来实现,而指定另一些成员拥有具体实现,子类继承后可以共用。
继承接口
类继承接口需要使用 implements
关键字,其语法如下。
class 类名称 implements 接口名称1, 接口名称2,...,接口名称n {
//类的成员以及接口的成员
}
例如,以下代码声明了一个 IUser
接口,它拥有两个待实现成员——name
字段和 login()
方法。GuestUser
继承了该接口,并实现了这两个接口成员,NormalUser
也继承了 IUser
接口,但区别在于,NormalUser
将 name
字段以存取器的方式实现。
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}`)
}
}
接口中可以指定可选成员,类在继承该接口后可以不实现已指定的可选成员,但这也意味着可选成员并不属于类的成员,因此不可以使用。例如,在以下代码中,接口 |
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
、只读存取器 name
和 myLocation()
方法,然后实例化 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() { }
}