文档章节

javaScript设计模式系列(一) 接口

Say_-no
 Say_-no
发布于 2017/06/23 17:10
字数 1976
阅读 10
收藏 1

前言

javaScript中并没有内置的类似java,C++的创建或实现接口的方法。也没有内置的方法可以用于判断一个对象是否实现了与另一个对象相同的方法,这使得对象很难互换使用。好在javaScrip有出色的灵活性,能让我我们模仿这些特性。

我们按照一下的顺序来逐步认识接口:

1.什么是接口?

2.javaScript模仿接口的三种方法。

3.接口的利弊

4.一个示例

什么是接口

接口提供了一种用以说明一个对象应该具有那些方法的手段,但并不规定这些方法应该如何实现。

好比一个厨师(chef),会三种菜 foodA,foodB,foodC, 我们很容易从语义上理解,这个厨师可以完成三种菜,但cooker并没有实现这三种菜的方法。

从代码上看类似于:

var chef= ['foodA','foodB','foodC'];

而不是:

var chef= {
  foodA:function(){
    //foodA实现方法
  },
  foodB:function(){
    //foodB实现方法
  },
  foodC:function(){
    //foodC实现方法
  }
}

这样我们可以利用接口这个工具,按照对象提供的特性对他们进行分组。

例如:一些对象存在着很大的差异,但是他们都实现了setName这个方法,就可以互换使用这些对象

var baseName = {
 name:'bird',
  someAttr:function(fn){
    this.name = fn;
  }
}
//这三个对象的差别很大,但都可以实现setName的方法
objA = {
  a:function(){},
  b:function(){},
  setName:function(newName){
    return newName;
  }
}
objB = {
  c:function(){},
  d:function(){},
  setName:function(newName){
    return newName;
  }
}
objC = {
  e:function(){},
  f:function(){},
  setName:function(newName){
    return newName;
  }
}
//可以互换使用这些对象,完成相同的效果
baseName.someAttr(objA.setName('dog'));
console.log(baseName.name);//dog
baseName.someAttr(objB.setName('dog'));
console.log(baseName.name);//dog
baseName.someAttr(objC.setName('dog'));
console.log(baseName.name);//dog

还可以使用接口来开发不同类之间的共同性。如果把原本要求以一个特定类为参数的函数改为要求以一个特定的接口为参数的函数,那么任何实现了该接口的对象都可以作为参数传递给它。如果理解了上面的例子,这个应该很容易理解。

模仿接口的三种方法

注释法

//    通过注释的方法描述接口
//    这种方法模仿其他面向对象语言中的做法,
//    使用interface(接口) 和 implements(实现) 关键字
//    但把他们放在注释中,以免引起语法错误

/*
 Interface ChefOne{
  function foodA(){}
  function foodB(){}
  function foodC(){}
}
Interface ChefTwo {
  function foodD(){}
  function foodE(){}
  function foodF(){}
}*/

//implement the ChefOne Interface  实现ChefOne接口
var ChefOne = function() {};
CookerOne.prototype.foodA = function() {};
CookerOne.prototype.foodB = function() {};
CookerOne.prototype.foodC = function() {};
//implement the ChefTwo Interface  实现ChefTwo接口
var ChefOne = function() {};
CookerOne.prototype.foodD = function() {};
CookerOne.prototype.foodE = function() {};
CookerOne.prototype.foodF = function() {};

注释描述接口,并没有去检查是否真的实现了接口,对接口的约定靠的都是认为的把控。虽然这不是一个好的方法,但是它易于实现,不需要额外的类和函数。不会影响文件的大小和执行速度。但是因为没有报错机制,对测试和调试没有帮助。

属性检查法

所有的类都明确的声明了自己实现哪些接口,可以针对这些声明进行检查。

接口自身仍然可以注释。

//有一个类TopChef  它自称实现了两个接口 chefOne chefTwo
var TopChef = function() {
  this.implementInterfaces = ['chefOne', 'chefTwo'];
  this.foodA = function() {};
  this.foodC = function() {};
}
//检查是否实现接口
function ensureImplements(obj) {
  for(var i = 1; i < arguments.length; i++) {
    var interfaceName = arguments[i];
    var interfaceFound = false;
    for(var j = 0; j < obj.implementInterfaces.length; j++) {
      if(obj.implementInterfaces[j] === interfaceName) {
        interfaceFound = true;
        break;
      }
    }
    if(!interfaceFound) {
      return false
    }
  }
  return true;
}
var topChef = new TopChef();

if(ensureImplements(topChef, 'chefOne', 'chefTwo')) {
  console.log('All interfaces were implement'); //所有的接口已经实现
} else {
  console.log('An interface was not implement'); //没有实现所有的接口
}

这种方法并没有确保类真正的实现了自己声称的接口,如果上面代码中 chefOne 有方法 foodA,chefTwo有方法foodC, 但是后期修改了TopChef的所实现的方法后,比如添加或者删除一个属性,仍然能通过检测,但是在接口中并不存在这个方法。很容易埋下隐患,这种错误也很难排插。

鸭式辨型法

这个名称来自James Whitcomb Riley 的名言:‘’像鸭子一样走路并且嘎嘎叫的就是鸭子‘’。

因此,可以理解为,如果对象具有与接口定义的方法同名的所有方法,就可以认为实现了这个接口。

//一个辅助函数构造接口,传入接口名称和属性
function Interface(interfaceName, mothodArr) {
  if(arguments.length < 2) {
    throw new Error('必须传入接口名称,接口方法两个参数');
  }
  this.name = interfaceName; //接口的名称
  this.mothodArr = []; //接口方法的数组
  for(var i = 0; i < mothodArr.length; i++) {
    if(typeof mothodArr[i] !== "string") {
      throw new Error("接口方法(" + mothodArr[i] + ")参数类型必须为字符串");
    }
    this.mothodArr.push(mothodArr[i]);
  }
}
//在interface上扩展接口检测的方法
Interface.ensureImplements = function(obj) {
  if(arguments.length < 2) {
    throw new Error("至少传入两个参数");
  }
  //从第二个参数循环,即为定义的接口
  for(var i = 1; i < arguments.length; i++) {
    var inter = arguments[i]; //拿到其中的一个接口
    //判断接口的构造函数是否正确
    if(inter.constructor !== Interface) {
      throw new Error(inter + "的构造函数必须是Interface");
    }
    //循环接口中定义的方法,与实现接口的对象中的方法做比较
    for(var j = 0; j < inter.mothodArr.length; j++) {
      var mothod = inter.mothodArr[j];
      //如果 方法不存在,或者不是一个函数 即终止程序
      if(!obj[mothod] || typeof obj[mothod] !== "function") {
        throw new Error("接口" + inter.name + "没有实现" + mothod + "方法");
      }
    }
  }
  console.log("所有接口的所有方法已经实现")
}
//定义两个接口
var gimMan = new Interface('gimMan', ['add', 'max', 'min']);
var givMan = new Interface('givMan', ['set']);

function CommMan() {
  this.add = function() {
    console.log(1)
  };
  this.max = function() {
    console.log(2)
  };
  this.min = function() {
    console.log(3)
  };
  //
  this.set = function() {
    console.log(4)
  };
  Interface.ensureImplements(this, gimMan, givMan);
}
var c = new CommMan(); //所有接口的所有方法已经实现

和以上两种方式不同,这种方法不需要注释,而且检测过程的大部分是可以强制的。如果漏掉了任何一个方法都会报错,而且会抛出相对有用的错误信息。

这种方法中,并不声明自己实现了哪些接口,因此没有自我描述性。它需要的是一个辅助类,和一个辅助函数。但这种方法是最常用,也是最完善的一种。

接口的利弊

利:

既定的一批接口具有自我描述性,并能促进代码的重用。

如果熟悉一个接口,就知道了所有实现它的类,从而有可能重用现有的类。

接口能让代码百年的更稳固。如果接口添加了一个操作,但是类中并没有实现它,很显然会得到一个错误。

弊:

接口会对性能造成一定影响。但是在项目生产环境中,可以去掉接口这部分代码。

javaScript不像其他语言中有接口的概念,无法根除是否实现着接口这个问题,还是需要大家的相互遵守。

 

示例

//有一个类它有转换或者处理字符串的方法
//参数gstr 是getString 方法的一个实例
//我们最基本的实现方法如下
var stringConvert = function(gstr){
  if(!(gstr instanceof getString)){
    throw new Error(gstr+'不是getString的实例');
  }
  this.gstr = gstr;
}
stringConvert.prototype.A = function(someString){
  return this.gstr.mothed1(someString);
}
stringConvert.prototype.B = function(someString){
  return this.gstr.mothed2(someString);
}
//这种写法会对gstr参数进行检查,但是不能保证mothed1 ,mothed2两个方法都已经实现
//而且如果有另一个类 getString2 更好的实现了这两种方法,也会因为instance of 检测不能使用 
//因此我们可以使用接口来代替instance of 像下面这样更好的实现

//用到上面的鸭式辨型法
//首先定义接口类
var getStr = new Interface('getStr',['mothed1','mothed2']);
var stringConvert = function(gstr){
  Interface.ensureImplements(gstr,getStr);
  this.gstr = gstr;
}
stringConvert.prototype.A = function(someString){
  return this.gstr.mothed1(someString);
}
stringConvert.prototype.B = function(someString){
  return this.gstr.mothed2(someString);
}

 

© 著作权归作者所有

Say_-no
粉丝 1
博文 6
码字总数 9308
作品 0
昆明
私信 提问
《JavaScript设计模式与开发实践》原则篇(2)—— 最少知识原则

最少知识原则(LKP)说的是一个软件实体应当尽可能少地与其他实体发生相互作用。这 里的软件实体是一个广义的概念,不仅包括对象,还包括系统、类、模块、函数、变量等。 单一职责原则指导我们...

嗨呀豆豆呢
2018/12/30
0
0
《JavaScript设计模式与开发实践》最全知识点汇总大全

系列文章: 《JavaScript设计模式与开发实践》基础篇(1)—— this、call 和 apply 《JavaScript设计模式与开发实践》基础篇(2)—— 闭包和高阶函数 《JavaScript设计模式与开发实践》模式...

嗨呀豆豆呢
01/04
0
0
JavaScript设计模式系列三之单例模式(附案例源码)

文章初衷 设计模式其实旨在解决语言本身存在的缺陷 目前javaScript一些新的语法特性已经集成了一些设计模式的实现, 大家在写代码的时候,没必要为了用设计模式而去用设计模式, 那么我这边为什...

小钱钱阿圣
2017/09/22
0
0
《JavaScript设计模式与开发实践》模式篇(12)—— 装饰者模式

在传统的面向对象语言中,给对象添加功能常常使用继承的方式,但是继承的方式并不灵活, 还会带来许多问题:一方面会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之 改变;另一方...

嗨呀豆豆呢
2018/12/25
0
0
《JavaScript设计模式与开发实践》模式篇(6)—— 命令模式

命令模式是最简单和优雅的模式之一,命令模式中的命令(command)指的是一个执行某些特定事情的指令。 应用场景 有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求...

嗨呀豆豆呢
2018/12/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

如何设计抗住100亿次请求的抢红包系统?(附GitHub代码)

1. 前言 前几天,偶然看到了 《扛住100亿次请求——如何做一个“有把握”的春晚红包系统”》一文,看完以后,感慨良多,收益很多。 正所谓他山之石,可以攻玉,虽然此文发表于2015年,我看到...

Java程序员之家
37分钟前
3
0
动图+源码,演示Java中常用数据结构执行过程及原理

最近在整理数据结构方面的知识, 系统化看了下Java中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于jdk8, 可能会有些特性与jdk7之前不相同, 例如LinkedList LinkedHashMap中的双向...

Java技术剑
今天
4
0
怎样在ps中制作对话气泡?一招教你轻松解决

PS是在工作中经常使用的平面设计软件,利用ps可以实现很多操作。换天,换发色,添加亮灯等操作都是比较常见的,今天将为大家分享怎样在ps中制作对话气泡的方法,希望能给大家带来帮助。 绘制...

干货趣分享
今天
2
0
EDI 电子数据交换全解指南

EDI(Electronic Data Interchange,电子数据交换)技术使得企业与企业(B2B)实现通信自动化,帮助交易伙伴和组织更快更好地完成更多工作,并消除了人工操作带来的错误。从零售商到制造商、物...

EDI知行软件
今天
3
0
CentOS7的LVM动态扩容

# 问题 CentOS7上面的磁盘空间有点紧张,需要扩容。 解决 查询当前磁盘状态 [root@xxx ~]# lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTfd0 2:0 1 4K ...

亚林瓜子
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部