JS设计模式---2.接口

什么是接口

接口提供了一种用以说明一个对象应该具有哪些方法的手段。

接口之利

  • 接口可以告诉一个程序员一个类实现了哪些方法,从而帮助其使用这个类。
  • 接口还有助于稳定不同类之间的通信方式。

接口之弊

  • 接口的使用在一定程度上强化了类型的作用,这降低了语言的灵活性。
  • JS 并没有提供对接口的内置支持,而试图模仿其他语言的内置功能总是会有一些风险
  • JS 中任何实现接口的方法都会对性能造成一定影响,在某种程度上这得归咎于额外的方法调用的开销
  • 接口的使用最大的问题在于,无法强迫其他程序员遵守你定义的接口

模仿接口实现

  • 用注释描述接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
interface Composite {
function add(child);
function remove(child);
function getChild(child);
}
interface FormItem {
function save(child);
}
*/
var CompositeFrom = function(id,method,action){
...
};
CompositeFrom .prototype.add = function(child){
...
};
CompositeFrom .prototype.remove= function(child){
...
};
CompositeFrom .prototype.getChild= function(child){
...
};
CompositeFrom .prototype.save= function(child){
...
};

这种做法易于实现,不需要额外的类或函数。可以提高代码的可重用性。

它的缺点在于没有为确保 CompositeFrom 真正实现了的正确方法而进行检查,也不会抛出错误告诉程序员程序中有问题,所以对于测试和调试也没有什么帮助。

  • 属性检查模仿接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
interface Composite {
function add(child);
function remove(child);
function getChild(child);
}
interface FormItem {
function save(child);
}
*/
var CompositeFrom = function(id,method,action){
//定义一个数组 存放将要实现的接口
this.implementsInterface = ['Composite','FormItem'];
...
};
function addForm(formInstance){
// 调用检查函数 如果存在未定义的接口 抛出错误
if(!implements(formInstance,'Composite','FormItem')){
throw new Error('Object does not implement a required interface')
}
...
}
function implements(object){
for (var i = 1;i<argumements.length;i++){
// 遍历参数 跳过第一个
var interfaceName = argument[i]; //接口名称
var interfaceFound = false ; //flag
for (var j=0;j<object.implementsInterface.length;j++){
// 遍历存放接口名称的数组
if(implementsInterface[j] == interfaceName){
interfaceFound = true;
break;
}
}
// 如果找到返回true 否则返回false
return interfaceFound
}
}

这种方法不仅对所实现的接口进行了注释说明,如果需要的接口不在一个类所宣称支持接口之内,它还会抛出一个错误,这样就可以强迫其它程序员声明这些接口。

它的缺点在于你不能保证所声明的接口是否真正实现。所以会存在检查通过,而方法不存在的问题。

  • 鸭式辨型模仿接口
1
2
3
4
5
6
7
8
9
10
11
12
// Interface
var Composite = new Interface('Composite',['add','remove','getChild']);
var FormItem= new Interface('FormItem',['save']);
//CompositeForm 类
var CompositeForm= function(id,method,action){
...
};
functiom addForm(formIntance){
// 辅助函数 如果引入未定义的接口会抛出错误
ensureImplements(formIntance,Composite,FormItem);
...
}

这种方法不依赖于注释。其各个方面都是可以强制实施的。ensureImplements 函数至少需要两个参数,第一个参数是想要检查的对象,其余参数是据以对那个对象进行检查的接口。

它的缺点在于,类并不声明自己实现了哪些接口,降低了代码的可重用性,也缺乏上面两种方法的自我描述性。它依赖于辅助类 Interface 和辅助函数 ensureImplements。

Interface 类的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 构造函数
var Interface = function(name,methods){
if(arguments.length != 2){
throw new Error(`Interface constructor called with ${arguments.length} arguments, but expcted 2.`);
}
this.name = name;
this.methods = [];
for(var i =0;i<methods.length;i++){
if(typeof methodsp[i] !== 'string'){
throw new Error("Interface constructor expects method name to be passed in as a string");
}
this.methods.push(methods[i]);
}
};
// 辅助函数
Interface.ensureImplements = function(object){
// 如果参数小于2个抛出错误
if(arguments.length<2){
throw new Error(`Function Interface.ensureImplements called with ${arguments.length} arguments, but expected at least 2.`);
}
for(var i = 1; i<arguments.length; i++){
var interface = arguments[i];
// 如果类的构造函数不是Interface 抛出错误
if(interface.constructor !== Interface){
throw new Error("Function Interface.ensureImplements expects arguments two and above to be insterface of Interface")
}
// 遍历存放接口名称的数组
for (var j=0;j<interface.methods.length;j++){
var method = interface.methods[j]
if(!object[method] || typeof method !== 'function'){
throw new Error(`Function Interface.ensureImplements: object does not implement the ${interface.name} interface.Method ${method} was not found`)
}
}
}
}

一个栗子

1
2
3
4
5
6
7
8

var DynamicMap =new interface('DynamicMap',['centerOnPoint','zoom','draw']);
function displayRoute(mapInterface) {
interface.ensureImplements(mapInterface,DynamicMap);
mapInterface.centerOnPoint(12,34);
mapInterface.zoom(5);
mapInterface.draw();
}