Javascript中如何实现interface机制?

我们知道,ECMAScript中是没有interface一说的。虽然如此,参考《Pro Javascript Design Pattern》一书,哥算是找到方案了。

最简单的方案,就是通过文档说明了。简称方案1,代码如下:

/*

interface composite{

function add(child);

function remove(child);

function getChild(index);

}

interface formItem{

function save();

}

*/

var compositeForm = function (id, name) {

this.id = id;

this.name = name;

if (typeof (compositeForm._initialized) === "undefined") {

// implement the composite interface.

compositeForm.prototype.add = function (child) {

console.log("added.");

};

compositeForm.prototype.remove = function (child) {

console.log("removed.");

};

compositeForm.prototype.getChild = function (index) {

return "Here is a child.";

};

// implement the formItem interface.

compositeForm.prototype.save = function () {

console.log("saved.");

};

compositeForm._initialized = true;

}

};

这种方案里,通过js的伪代码,定义好composite和formItem 2个接口,然后在 compositeForm 类中通过动态原型来实现接口中定义的方法。以下是简单的几行测试代码:

var item = new compositeForm(1, "form 1");

console.log(item.id);

item.add(null);

item.save();

var item2 = new compositeForm(2, "form 2");

console.log(item.add == item2.add);

对于具有特强执行力和完全可控制的团队来说,完全没有问题。问题是,21世纪有这样的团队吗?即便有,IT圈里的人员变动也不能保证它一如既往啊。改进的方案就是,通过约定和校验了。作出改进(简称方案2),详细如下:

/*

interface composite{

function add(child);

function remove(child);

function getChild(index);

}

interface formItem{

function save();

}

*/

var compositeForm = function (id, name) {

this.id = id;

this.name = name;

if (typeof (compositeForm._initalized) === "undefined") {

// implement the composite interface.

compositeForm.prototype.add = function (child) {

console.log("added.");

};

compositeForm.prototype.remove = function (child) {

console.log("removed.");

};

compositeForm.prototype.getChild = function (index) {

return "Here is a child.";

};

// implement the formItem interface.

compositeForm.prototype.save = function () {

console.log("saved.");

};

//sign for implemented interfaces

compositeForm.prototype.implementsInterfaces = ["composite", "formItem"];

compositeForm._initalized = true;

}

};

function implements(obj, interfaces) {

if (obj == null || typeof (obj) !== "object") {

throw new Error("obj must to be a object.");

}

if(interfaces == null || !(interfaces instanceof Array)){

throw new Error("interfaces must to be a Array");

}

for(item in interfaces){

if(typeof(interfaces[item]) !== "string"){

throw new Error("interfaces must to be a string Array");

}

}

var result = true;

if (interfaces.length > 0) {

if (typeof (obj.implementsInterfaces) === "undefined" || !(obj.implementsInterfaces instanceof Array) || obj.implementsInterfaces.length < 1) {

result = false;

} else {

for (item in interfaces) {

var itemResult = false;

for (funIndex in obj.implementsInterfaces) {

if (interfaces[item] == obj.implementsInterfaces[funIndex]) {

itemResult = true;

break;

}

}

if (!itemResult) {

result = false;

break;

}

}

}

}

return result;

}

// Validate instace. If invalid, throw exception.

function addForm(formInstance) {

if (!implements(formInstance, ["composite", "formItem"])) {

throw new Error("Object doesn't implement the interfaces.");

}

//...

}

它的核心就是:compositeForm.prototype.implementsInterfaces = ["composite", "formItem"];通过定义implementsInterfaces属性,并将它赋值为已实现的接口名称的数组。而implements方法就是通过对比对象实体的implementsInterfaces属性和需要实现的接口名称逐一进行对比,来判断实体所属类是否真的实现了指定接口。但是,如果给implementsInterfaces赋值说已实现了某接口,但是并没有实现它的方法,怎么办?

继续改进,有方案3:

/*

interface composite{

function add(child);

function remove(child);

function getChild(index);

}

interface formItem{

function save();

}

*/

// interface class

var Interface = function (name, methods) {

if (arguments.length != 2) {

throw new Error("Interface constructor called with " + arguments.length + "arguments, but expected exactly 2.");

}

if (typeof name !== "string") {

throw new Error("Interface constructor expected name to be passed in as a string.");

}

if (methods == null || !(methods instanceof Array)) {

throw new Error("Interface constructor expected methods to be passed in as a method.");

}

this.name = name;

this.methods = methods;

};

Interface.ensureImplements = function (classFun, interfaces) {

if (arguments.length != 2) {

throw new Error("Function Interface.ensureImplements called with " + arguments.length + "arguments, but expected 2.");

}

if (classFun == null || typeof (classFun) != "object") {

throw new Error("classFun expected to be passed in a object.");

}

if (interfaces == null || !(interfaces instanceof Array)) {

throw new Error("interfaces expected to be passed in a Array.");

}

for (index in interfaces) {

if (!(interfaces[index] instanceof Interface)) {

throw new Error("interfaces[" + index + "] expected to be passed in a Interface.");

}

var currentInterface = interfaces[index];

for (methodIndex in currentInterface.methods) {

var methodName = currentInterface.methods[methodIndex];

if (!classFun[methodName] || typeof (classFun[methodName]) != "function") {

return false;

}

}

}

return true;

};

// define two interfaces

var compsite = new Interface("compsite", ["add", "remove", "getChild"]);

var formItem = new Interface("formItem", ["save"]);

// a class implements above two interfaces

var compositeForm = function (id, name) {

this.id = id;

this.name = name;

if (typeof (compositeForm._initialized) === "undefined") {

// implement the composite interface.

compositeForm.prototype.add = function (child) {

console.log("added.");

};

compositeForm.prototype.remove = function (child) {

console.log("removed.");

};

compositeForm.prototype.getChild = function (index) {

return "Here is a child.";

};

// implement the formItem interface.

compositeForm.prototype.save = function () {

console.log("saved.");

};

compositeForm._initialized = true;

}

};

// Validate instace. If valid, alert true. Else, throw exception.

function addForm(formInstance) {

if (!Interface.ensureImplements(formInstance, [compsite, formItem])) {

throw new Error("Object doesn't implement the interfaces.");

}

//...

alert('true');

}

这里定义了Interface类,并实现了类的ensureImplements静态方法。关于Interface类的实现,可以独立作为一个文件,以后多个地方可以用到。然后,通过下面的代码来定义接口:

var compsite = new Interface("compsite", ["add", "remove", "getChild"]);

var formItem = new Interface("formItem", ["save"]);

至于compositeForm类自身的实现,除了不再需要implementsInterfaces签名,其他和之前的几乎一样。使用时,通过调用:

if (!Interface.ensureImplements(formInstance, [compsite, formItem])) {

throw new Error("Object doesn't implement the interfaces.");

}

来判断是否真的实现了指定接口。它不仅可以判断到接口,还可以判断到方法。

至此,不仅可以定义interface,还可以ensure implement了。 (注:以上方法主题思路来源于Pro Javascript Design Pattern,但同时修复了它里面的一些细小问题) 完整代码download