一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情。
接口用于约束类、对象、函数, 是一个类型契约。前面的文章将了接口约束其它类型, 今天了解接口如何约束类。
接口怎么配合类
结合一个例子来描述接口配合类的好处:
假如有这样一种场景,在马戏团中有很多动物它们都具有一下特征:
- 年龄
- 种类
- 昵称
这里就可以创建一个 动物类 抽象类描述上面的特征:
//动物类
abstract class Animal{
abstract age:number //年龄
abstract name:string //名字
abstract nickname:string //昵称
}
复制代码
这里创建 狮子、猴子、狗类 继承至 动物类
//动物类
abstract class Animal {
abstract age: number //年龄
abstract type: string //名字
abstract nickname: string //昵称
}
class Lion extends Animal {
type: string = '食肉目猫科狮亚属的大型动物'
constructor(public age: number, public nickname: string) {
super()
}
}
class Monkey extends Animal {
type: string = '哺乳纲灵长目'
constructor(public age: number, public nickname: string) {
super()
}
}
class Dog extends Animal {
type: string = '犬科哺乳动物'
constructor(public age: number, public nickname: string) {
super()
}
}
复制代码
然后分别创建这些动物
const lion = new Lion(2, '小白')
const monkey = new Monkey(1, '复航')
const dog = new Dog(1, '旺财')
复制代码
现在马戏团对这些动物进行专业的训练
- 小白学会了跳火圈
- 复航学会了说脱口秀
- 旺财学会了做数学题
这就代表着它们三有了动物不具备的特殊 技能(skill), 所以上面创建 狮子、猴子、狗的类需要加上这些动物不具备的能力,改造一下 class 的实现。
class Lion extends Animal {
type: string = '食肉目猫科狮亚属的大型动物'
constructor(public age: number, public nickname: string) {
super()
}
//跳火圈能力
tiàoHuǒQuān() {
console.log(`${this.nickname}会跳火圈`)
}
}
class Monkey extends Animal {
type: string = '哺乳纲灵长目'
constructor(public age: number, public nickname: string) {
super()
}
//脱口秀能力
tuokouxiu() {
console.log(`${this.nickname}会脱口秀`)
}
}
class Dog extends Animal {
type: string = '犬科哺乳动物'
constructor(public age: number, public nickname: string) {
super()
}
//数学题能力
shuxueti() {
console.log(`${this.nickname}会数学题`)
}
}
复制代码
此时马戏团开始上台表演了:
- 请会跳火圈的动物表演
那么问题来了, 代码该怎么写? 也许会这样写:
const animals: Animal[] = [lion, monkey, dog]
animals.forEach(a => {
if ((a as Lion).tiàoHuǒQuān) {
(a as Lion).tiàoHuǒQuān()
}
})
复制代码
从逻辑上这里判断了某一个动物是否具有 tiàoHuǒQuān
方法,而不是拥有 会跳火圈的能力 从逻辑上这个判断就已经是错误的, 只要逻辑错误代码就必然存在隐患。
解决方法: 类配合接口
定义接口
//跳火圈
interface IJump {
tiàoHuǒQuān(): void
}
//脱口秀
interface ITalkShow {
tuokouxiu(): void
}
//数学题
interface IMath {
shuxueti(): void
}
复制代码
只要类使用了该接口那么类就一定要实现接口的成员方法
接口配合类使用
implements 接口名
class Lion extends Animal implements IJump {
type: string = '食肉目猫科狮亚属的大型动物'
constructor(public age: number, public nickname: string) {
super()
}
//跳火圈能力
tiàoHuǒQuān() {
console.log(`${this.nickname}会跳火圈`)
}
}
复制代码
Lion
类有 IJump
进行强约束, 所以Lion
类必须实现Ijump
的成员tiàoHuǒQuān
否在在ts中则报错。
现在回归到马戏团表演的问题
此时马戏团开始上台表演了:
- 请会跳火圈的动物表演
那么问题来了, 代码该怎么写?
animals.forEach(a => {
if(a instanceof IJump){
}
})
复制代码
上面的写法通过关键字instanceof
判断 a
是否有 IJump
的所有成员在 java, c## 语言中是允许的, 但在 ts 中则不合法(因为接口不存在编译结果中), 为了更好的书写这个判断这里可以写一个函数(类型保护函数)专门用于判断 a
是不是某种类型。
类型保护函数
//判断是否游跳火圈的能力
function hasIjump(animal: object): animal is IJump {
return (animal as IJump).tiàoHuǒQuān !== undefined
}
复制代码
有了这个类型保护函数的辅助, 接下来的一切将会变得很自然
animals.forEach(a => {
if (hasIjump(a)) { //这里的语义化就是判断 a 动物是否拥有跳火圈的能力
a.tiàoHuǒQuān()
}
})
复制代码
在后续的训练中 狗 也学会了跳火圈的能力, 那么对代码轻微了修改
class Dog extends Animal implements IJump, IMath {
type: string = '犬科哺乳动物'
constructor(public age: number, public nickname: string) {
super()
}
//跳火圈能力
tiàoHuǒQuān(): void {
console.log(`${this.nickname}会跳火圈`)
}
//数学题能力
shuxueti() {
console.log(`${this.nickname}会数学题`)
}
复制代码
现在 狗 拥有了跳火圈的能力, 但并不会印象其他代码的执行, 这就是接口配合类的好处,更容易扩展。
接口可以继承类
class A {
a1: string = ''
a2: string = ''
a3: string = ''
}
class B {
b1: number = 0
b2: number = 0
b3: number = 0
}
interface C extends A, B {
}
const c:C = {
a1: '',
a2: '',
a3: '',
b1: 0,
b2: 0,
b3: 0
}
复制代码
总结
类型保护函数: 通过调用该函数、会触发TS的累心保护、该函数必须返回 boolean
接口和类型别名的最大区别: 接口可以被类显示、而类型别名不可以。