Javascrit Interfaces
参考书籍: 一本老书(相当老,十年前的书) 《Pro JavaScript Design Patterns》第二章 Interfaces
参考网站: 阮一峰老师的开源书籍《ECMAScript 6 入门》网站 module的语法 与函数的扩展
这本老书这一章主讲接口,以及如何使用javascript这门语言来模拟常规面向对象编程方法中的接口.
总共列举了三种方式:
1. 使用注释来描述接口
2. 使用属性检查来模拟接口
3. 使用鸭式类型来模拟接口(一个对象看起来像鸭子对象,但实际不是)
这三种方式没有一种是完全完美的哦,但组合使用三种方法可以尽可能地接近接口的样子
接口是个啥?
接口是定义一个对象应该包含哪些方法的一种方式,它并不定义这些方法的实现
使用接口的好处
创建接口可以自成文档以及提升可重用性
1.1使用注释描述接口
出于模仿其他面向对象的语言的目的,我们使用interface 和implements 这两个来描述相关方法
优点:这很容易实现,不需要其他的类或方法,不会增加代码的size,不会影响代码的执行速度
缺点:使用该类的实例时,没有方法可以检查类的接口是否定义正确,当有错误的时候也没有错误抛出,不利于调试
1.1.1旧语法版本
//下面这个大的注释代码块,就是模拟的其他语言的接口定义方式,
//在javascript中并没有这样的语法,能看得懂就好
/*
interface Composite{
function add(child);
function remove(child);
function getChild(index);
}
interface FormItem{
function save();
}
*/
//当当当,这里在定义一个构造函数,
//简单地使用注释说明这个类中应该包含并实现哪些接口中的方法
//代码中使用注释的目的,review过代码的人都知道注释的重要性 ,所以这种注释有比没有好!
//在定义构造函数的代码块处注释 需要实现哪些接口
//implements Composite,FormItem
var CompositeForm =function(id,method,action){
//...
}
//在定义实现具体的方法代码块处注释,实现的是哪一个接口
//Implements the Composite interface.
CompositeForm.prototype.add = function(child){
//...
}
CompositeForm.prototype.remove = function(child){
//...
}
CompositeForm.prototype.getChild = function(index){
//...
}
//Implements the ForeItem interface
CompositeForm.prototype.save = function (){
}
1.1.2 es6语法版本
/*
interface Composite{
function add(child);
function remove(child);
function getChild(index);
}
interface FormItem{
function save();
}
*/
class CompositeForm{ //implements Composite,FormItem
constructor(id,method,action){
//...
}
//Implements the Composite interface.
add(child){
//...
}
remove(child){
//...
}
getChild(index){
//...
}
//Implements the ForeItem interface
save(){
}
}
1.2使用属性检查来模拟接口
优点:你通过对象中的一个属性显式地记录了一个类需要实现哪些接口,
不再是注释(注释有可能被压缩去掉)可以随时在别处对该类的实例进行检测,
通过报错来检查是否有需要的接口却在定义相关属性的时候给漏掉了
(连这个都漏掉了,那么没添加相关接口方法的概率是不是更大?)
1.2.1旧语法版本
// 依然使用注释代码块来描述一个接口里有哪些方法
/*
interface Composite{
function add(child);
function remove(child);
function getChild(index);
}
interface FormItem{
function save();
}
*/
//写一个工具函数,来检查相关属性
//implements函数用来检测一个对象中的相关属性
//检查属性implementsInterfaces是否显示定义了需要的接口
function implements(object){
//注意:i初始化数值为1哦
//跳过第一个参数,循环剩下的所有参数,缺省的参数为要检查的接口名字
for(var i=1;i < arguments.length;i++){
var interfaceName = arguments[i];
var interfaceFound = false;
for(var j=0;j < object.implementsInterfaces.length;j++){
if(object.implementsInterfaces[j]==interfaceName){
interfaceFound=true;
break; //找到就跳出当前循环
}
}
if(!interfaceFound){
return false; //有一个接口没有找到就返回false
}
}
return true; //所有接口都倒找了返回true
}
//这里在定一个构造函数,函数体内定义属性implementsInterfaces,
//保存这个类需要实现哪些接口
var CompositeForm = function(id,method,action){
this.implementsInterfaces=['Composite','FormItem'];
//...
}
//这是某处的一个函数,参数为某个类的实例对象,
//在函数体内会调用这个实例对象的某些方法
//在调用这个实例对象的某些方法前使用工具函数implements先检查下有木有
function addForm(formInstance){
if(!implements(formInstance,'Composite','FormItem')){
throw new Error('addForm中的foremInstance说:
哇~要么是addForm你想要的太多,
要么就是创造我的那个傻猿忘记记录要实现某些接口.');
}
//...
}
1.2.2es6语法版本
//rest 参数(形式为"...变量名"),用于获取函数的多余参数,
//rest参数搭配的变量是一个数组,rest参数之后不能再有其他参数
//这里之所以要命名函数为implementsss,还有参数变量interfacess
//单词后面加那么多sss是因为es6采用严格模式,
//implements和interfaces是保留关键词
function implementsss(object,...interfacess){
//直接循环interfacess这个变量,
//不用像旧语法版本中那样显示定义到底从第几个参数开始循环
for(var i=0,len=interfacess.length;i<len;i++){
var interfaceName = interfacess[i];
var interfaceFound = false;
for(var j=0;j<object.implementsInterfaces.length;j++){
if(object.implementsInterfaces[j]==interfaceName){
interfaceFound=true;
break; //找到就跳出当前循环
}
}
if(!interfaceFound){
return false; //有一个接口没有找到
}
}
return true; //所有接口都倒找了
}
//这里在定一个类,构造函数体内定义属性implementsInterfaces,
class CompositeForm{
constructor(id,method,action){
this.implementsInterfaces=['Composite','FormItem'];
}
}
function addForm(formInstance){
if(!implementsss(formInstance,'Composite','FormItem')){
throw new Error('addForm中的foremInstance说:
哇~要么是addForm你想要的太多,
要么就是创造我的那个傻猿忘记记录要实现某些接口.');
}
//...
}
1.3使用鸭式类型来模拟接口
优点:不需要任何注释代码,当使用相关实例的方法前
使用ensureImplements方法检测其是否支持相关方法
缺点:
1. 类中没有明确指出它需要实现哪些接口;
2. 会降低性能;这需要额外的Interface帮助类,以及一个帮助方法ensureImplements;
3. 并不会检测方法内的参数个数以及类型,仅仅检测方法名是否正确
1.3.1 旧语法版本
//定义接口帮助类
var Interface = function (name,method){
//检查参数个数是否正确
if(arguments.length!=2){
throw new Error('Interface contructor
被调用时使用了'+arguments.length +'个参数,实际需要2个参数')
}
this.name = name;
this.method = [];
for(var i = 0,len= method.length;i<len;i++){
//检测参数类型
if(typeof method[i] !=='string' ){
throw new Error ('Interface contructor 需要的方法名为字符串类型')
}
this.method.push(method[i]);
}
}
//静态类方法
Interface.ensureImplements = function(object){
//检测参数长度
if(arguments.length<2){
throw new Error ('Interface.ensureImplements方法调用时
使用了'+arguments.length+"个参数,实际需要至少需要两个参数")
}
//遍历接口 检测是否为接口实例
for(var i=1,len=arguments.length;i<len;i++){
var interface = arguments[i];
if(interface.constructor !==Interface){
需要第二个及后面的参数必须是Interface的实例');
}
for(var j = 0,methodLen=interface.method.length;j<methodLen;j++){
var method = interface.method[j];
//检测属性是否存在且是否是函数
if(!object[method] || typeof object[method] !='function'){
throw new Error('Function Interface.ensureImplements:object
接口方法"+interface.method+'没找到')
}
}
}
}
//接口帮助类的使用实例
//定义要用到的Interfaces
var Composite = new Interface('Composite',['add','remove','getChild']);
var FormItem = new Interface('FormItem',['save']);
//CompositeForm class
//implements Composite,FormItem
var CompositeForm = function(id,method,action){
//...
};
function addForm(fromInstance){
Interface.ensureImplements(fromInstance,Composite,FormItem);
//当有一个方法没有实现时会抛出一个错误
//停止执行函数
//只有当检测通过时,余下的代码才会被执行
//...
}
1.3.2 es6 语法版本
//接口类
//Constructor
class Interface {
constructor(name, method) {
//检查参数个数是否正确
if (arguments.length != 2) {
throw new Error('Interface contructor 被调用时使用了' +
arguments.length + '个参数,实际需要2个参数')
}
this.name = name;
this.method = [];
for (let i = 0, len = method.length; i < len; i++) {
//检测参数类型
if (typeof method[i] !== 'string') {
throw new Error('Interface contructor
需要的方法名为字符串类型')
}
this.method.push(method[i]);
}
}
}
//类的静态方法
Interface.ensureImplements = function(object,...interfaces) {
//检测参数长度
if (arguments.length < 2) {
throw new Error('Interface.ensureImplements方法调用时使用了' +
arguments.length + "个参数,实际需要至少需要两个参数")
}
//遍历接口 检测是否为接口实例
for (let i = 0, len = interfaces.length; i < len; i++) {
let interfacel = interfaces[i];
if (interfacel.constructor !== Interface) {
throw new Error('Interface.ensureImplements 方法
需要第二个及后面的参数必须是Interface的实例');
}
for (let j = 0, methodLen = interfacel.method.length;
j < methodLen; j++) {
let method = interfacel.method[j];
//检测属性是否存在且是否是函数
if (!object[method] || typeof object[method] != 'function') {
throw new Error('Function Interface.ensureImplements:
object 没有实现' + interfacel.name +
"接口,接口方法" + interfacel.method + '没找到')
}
}
}
}
//导出模块
//es6模块功能主要由两个命令构成:export和import。
//export命令用于规定模块的对外接口,
//import命令用于输入其他模块提供的功能。
//import命令输入的变量都是只读的,因为它的本质是输入接口。
//也就是说,不允许在加载模块的脚本里面,改写接口。
//export {Interface}; //es6语法
module.exports={Interface:Interface}; // nodejs语法
//导入模块
// import {Interface} from './Interface.js'; //es6语法
var Interface = require('./Interface').Interface; //nodejs 语法
var Composite = new Interface('Composite',['add','remove','getChild']);
var FormItem = new Interface('FormItem',['save']);
class CompositeForm3{
constructor(id,method,action){
}
}
function addForm3(fromInstance){
Interface.ensureImplements(fromInstance,Composite,FormItem);
}
在es6语法版javascript设计模式练习计划查看初始内容