伍.1.6 用JavaScript实现接口

JavaScript并不支持接口(interface),然而在构建大型框架/库的时候,我们很需要想办法实现接口的特性:

  • 实现接口的类必须实现了接口中的方法;

  • 有办法检测到该类的实例的类型,并且这个类型和接口(约定的)类型值相等。

01.TypeScript的实现

由于JavaScript没有接口,因此,我将向你展示TypeScript如何实现此目的,因为TypeScript补全了JavaScript面向对象编程的能力,以和JavaScript面向原型的一个分支领域区别开来。

interface ShapeInterface {
area(): number
}
class Circle implements ShapeInterface {
let radius: number = 0
constructor (r: number) {
this.radius = r
}
public area(): number {
return MATH.PI * MATH.pow(this.radius, 2)
}
}

在上面的示例中,展示了如何在TypeScript中实现此目标,但是TypeScript在后台将代码编译为纯JavaScript,而在已编译的代码中,由于JavaScript没有接口,因此也竟然没有给出接口有关的表现形式。真是扫兴啊!

02.JavaScript的实现

那么在JavaScript缺乏接口的情况下,我们如何实现这一目标呢?

通过函数组合可以实现。

首先,我们创建shapeInterface工厂函数,因为我们在谈论接口,所以我们的shapeInterface将使用函数组合出像接口一样抽象体。

什么是工厂函数?

在JavaScript中,函数若返回一个对象,且被返回的对象既不是构造函数、也不是某一个类,那么这该函数被称作工厂函数。

const shapeInterface = (state) => ({
type: 'shapeInterface',
area(){
if(state.area && typeof state.area === "function")
return state.area(state);
else
throw new Error("必须实现接口shapeInterface的方法area");
}
})

然后我们让正方形(square)工厂函数实现上面的接口。

const square = (length) => {
const proto = {
length,
type : 'Square',
area : (args) => Math.pow(args.length, 2)
}
const basics = shapeInterface(proto)
const composite = Object.assign({}, basics)
return Object.assign(Object.create(composite), {length})
}

接着调用工厂函数square后,结果应该是下面这样的:

const s = square(5)
console.log('OBJ\n', s)
console.log('PROTO\n', Object.getPrototypeOf(s))
s.area()
// 输出
//>> OBJ
//>> { length: 5 }
//>> PROTO
//>> { type: 'shapeInterface', area: [Function: area] }
//>> 25

如此一来,我们可试着做一个产品经理要求的功能:计算面积之和的。其间可以检测具体的对象否有没有实现接口:

//计算面积之和
sum() {
const area = []
for (shape of this.shapes) {
//用Object.getPrototypeOf来检测类型
if (Object.getPrototypeOf(shape).type === 'shapeInterface') {
area.push(shape.area())
} else {
throw new Error('该对象不是接口shapeInterface的实例')
}
}
return area.reduce((v, c) => c += v, 0)
}

03.结语

最后再次说一下,由于JavaScript不支持强类型语言之类的接口,上面的示例演示了我们如何模拟它,并不能十分完美的实现接口的各种特性。这也许是语言本身的局限,也有待你我探索。