Javascript乱弹设计模式系列(0) - 面向对象基础以及接口和继承类的实现

前言

博客园谈设计模式的文章很多,我也受益匪浅,包括TerryLee吕震宇等等的.NET设计模式系列文章,强烈推荐。对于我,擅长于前台代码的开发,对于设计模式也有一定的了解,于是我想结合Javascript来设计前台方面的“设计模式”,以对后台“设计模式”做个补充。开始这个系列我也诚惶诚恐,怕自己写得不好,不过我也想做个尝试,一来希望能给一些人有些帮助吧,二来从写文章中锻炼下自己,三来通过写文章对自己增加自信;如果写得不好,欢迎拍砖,我会虚心向博客园高手牛人们学习请教;如果觉得写得还可以,谢谢大家的支持了:)

在开始设计模式的书写之前,有必要对Javascript面向对象的概念先做个介绍,那么这篇文章就以面向对象基础作为起点吧。

那好了,闲话不多说了,开始这系列的开篇文章

理论知识

1. 首先Javascript是弱类型语言,它定义变量时不必声明类型,如var Person = new Person(),它的变量类型为“var”,现在的C# 3.0也引进了这种匿名类型的概念,弱类型的变量产生了极大的灵活性,因为Javascript会根据需要来进行类型转换。所以这也决定了它采用了晚绑定的方法,即在运行后才知道变量的类型;

2. 面向对象概念不必多说,封装,继承,多态;

3. Javascript对象的类型主要分为三种:本地对象,如String,Array,Date等;内置对象,如Global,Math等;宿主对象,是指BOM,DOM对象等等;变量范围包括传统面向对象程序设计中的作用域,如公有,保护,私有,静态等等;

主要内容

1. 现在让我们来看看Javascript怎样创建对象的:

function  Man() {
   
//   
}
Man.prototype.getNickName 
=   function () {
    
return   " Leepy " ;
}; 

var  man  =   new  Man();
var  name  =  man.getNickName(); 

这样就创建了最简单的类和对象,其中我们可以把function Man() {} 看作是Man类的构造函数,getNickName()看作是Man类的方法,准确说可以“当作”是Man类的公共方法;为什么要说是当作呢?那是因为其实Javascript实际上并没有一个私有共有的划分,因此开发者们自己指定了这样的规约,那么规约是什么样的呢?我这里把Man类的清单完整地列出来:

function  Man() {
    
//  私有静态属性
     var  Sex  =   " " ;
    
// 私有静态方法
     function  checkSex() {
        
return  (Sex  ==   " " );
    }
    
// 私有方法
     this ._getSex  =   function () {
        
// 调用私有静态方法
         if (checkSex())
            
return   " " ;
        
else
            
return   " " ;
    }
    
// 私有方法
     this .getFirstName  =   function () { 
        
return   " Li " ;
    };
    
// 私有方法
     this .getLastName  =   function () {
        
return   " Ping " ;
    };
}
// 公共方法
Man.prototype.getNickName  =   function () {
    
return   " Leepy " ;
};
// 公共方法
Man.prototype.getFullName  =   function () {
    
return   this .getFirstName()  +   "   "   +   this .getLastName();
};
// 公共方法
Man.prototype.getSex  =   function () {
    
// 调用私有方法
     return   this ._getSex();
};
// 公共静态方法
Man.say  =   function () {
    
return   " Happy new year! " ;
}

这样的类是否看起来和传统的类很相似了呢^_^

2. 接下来这个是本篇的一个重点,就是用Javascript如何设计一个接口,然后让类继承于它。

首先,先让我们看传统的C#语言是如何设计接口的吧:

public   interface  Person
{
    
string  GetName();
    
void  SetName( string  name);
}
public   class  Man : Person
{
    
private   string  _name; 

    
public   string  GetName()
    {
        
return  _name;
    }
    
public   void  SetName( string  name)
    {
        _name 
=  name;
    }

接口中可以声明属性、方法、事件和类型(Structure),(但不能声明变量),但是并不能设置这些成员的具体值,也就是说,只能定义,不能给它里面定义的东西赋值,而接口作为它的继承类或者派生类的规约,继承类或者它的派生类能够共同完成接口属性、方法、事件和类型的具体实现,因为这里GetName(),SetName(),不管是方法名还是属性调用顺序上都是要保持一致的;

那么有了这样的一个基于接口的思想,我们设计Javascript的接口类的时候也需要考虑到这个规范。我先从主JS文件调用端开始说起:

var  Person  =   new  Interface( " Person " , [[ " getName " 0 ], [ " setName " 1 ]]); 

其中Interface类是稍后要说的接口类,第一个参数"Person"是接口类的名称,第二个参数是个二维数组,"getName"是接口方法的名称,"0"是该方法所带的参数个数(因为Javascript是弱语言,所以类型是不确定的,所以只要记住参数个数就好,"0"可以省略不写),"setName"同理。这样一个接口定义好了。怎样使用它呢?

function  Man() 
{

    
this .name  =   "" ;
    Interface.registerImplements(this, Person);
}
Man.prototype.getName 
=   function () {
    
return   this .name;
};
Man.prototype.setName 
=   function (name) {
    
this .name  =  name;
}; 

看到Man的构造函数里面包含

Interface.registerImplements(this, Person);

它是用来将实例化的this对象继承于Person接口,然后继承类对接口的方法进行实现。

代码看起来是不是很清晰和简单呢^_^,那么现在要开始介绍真正的核心代码Interface.js了:

先看Interface的构造函数部分

function  Interface(name, methods) 
{
    
if (arguments.length  !=   2 ) {
        
throw   new  Error( " 接口构造函数含 "   +  arguments.length  +   " 个参数, 但需要2个参数. " );
    }
    
this .name  =  name;
    
this .methods  =  [];
    
if (methods.length  <   1 ) {
        
throw   new  Error( " 第二个参数为空数组. " );
    }
    
for ( var  i  =   0 , len  =  methods.length; i  <  len; i ++ ) {
        
if ( typeof  methods[i][ 0 !==   ' string ' ) {
            
throw   new  Error( " 接口构造函数第一个参数必须为字符串类型. " );
        }
        
if (methods[i][ 1 &&   typeof  methods[i][ 1 !==   ' number ' ) {
            
throw   new  Error( " 接口构造函数第二个参数必须为整数类型. " );
        }
        
if (methods[i].length  ==   1 ) {
            methods[i][
1 =   0 ;
        } 

        
this .methods.push(methods[i]);
    }    
};

刚才看到了var Person = new Interface("Person", [["getName", 0], ["setName", 1]]);,这里将两个参数分别保存起来;

调用方法部分

Interface.registerImplements  =   function (object) { 

    
if (arguments.length  <   2 ) {
        
throw   new  Error( " 接口的实现必须包含至少2个参数. " );
    } 

    
for ( var  i  =   1 , len  =  arguments.length; i  <  len; i ++ ) {
        
var  interface  =  arguments[i];
        
if (interface.constructor  !==  Interface) {
            
throw   new  Error( " 从第2个以上的参数必须为接口实例. " );
        }
        
for ( var  j  =   0 , methodsLen  =  interface.methods.length; j  <  methodsLen; j ++ ) {
            
var  method  =  interface.methods[j][ 0 ];
            
if ( ! object[method]  ||   typeof  object[method]  !==   ' function '   ||  object[method].getParameters().length  !=  interface.methods[j][ 1 ]) {
                
throw   new  Error( " 接口的实现对象不能执行 "   +  interface.name  +   " 的接口方法 "   +  method  +   " ,因为它找不到或者不匹配. " );
            }
        }
    }
}; 

刚才这句Interface.registerImplements(this, Person);,实际上这里是把this对象的方法名以及参数个数与刚Person保存的methods逐一进行比较,如果找不到或者不匹配,就警告错误;其中object[method].getParameters().length,调用了如下的代码:

Function.prototype.getParameters  =   function () { 

    
var  str  =   this .toString();
    
var  paramString  =  str.slice(str.indexOf( ' ( ' +   1 , str.indexOf( ' ) ' )).replace( / \s* / g, '' );      // 取得参数字符串
     try
    {
        
return  (paramString.length  ==   0   ?  [] : paramString.split( ' , ' ));
    }
    
catch (err)
    {
        
throw   new  Error( " 函数不合法! " );
    }

 getParrameters()方法作为Function对象的一个扩展,功能是取得方法含有的参数数组;

Interface.js完整的代码如下:

ContractedBlock.gif ExpandedBlockStart.gif Interface.js文件
function Interface(name, methods) 
ExpandedBlockStart.gifContractedBlock.gif
{
ExpandedSubBlockStart.gifContractedSubBlock.gif    
if(arguments.length != 2{
        
throw new Error("接口构造函数含" + arguments.length + "个参数, 但需要2个参数.");
    }

    
this.name = name;
    
this.methods = [];
ExpandedSubBlockStart.gifContractedSubBlock.gif    
if(methods.length < 1{
        
throw new Error("第二个参数为空数组.");
    }

ExpandedSubBlockStart.gifContractedSubBlock.gif    
for(var i = 0, len = methods.length; i < len; i++{
ExpandedSubBlockStart.gifContractedSubBlock.gif        
if(typeof methods[i][0!== 'string'{
            
throw new Error("接口构造函数第一个参数必须为字符串类型.");
        }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
if(methods[i][1&& typeof methods[i][1!== 'number'{
            
throw new Error("接口构造函数第二个参数必须为整数类型.");
        }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
if(methods[i].length == 1{
            methods[i][
1= 0;
        }
 

        
this.methods.push(methods[i]);
    }
    
}


ExpandedBlockStart.gifContractedBlock.gifInterface.registerImplements 
= function(object) 

ExpandedSubBlockStart.gifContractedSubBlock.gif    
if(arguments.length < 2{
        
throw new Error("接口的实现必须包含至少2个参数.");
    }
 

ExpandedSubBlockStart.gifContractedSubBlock.gif    
for(var i = 1, len = arguments.length; i < len; i++{
        
var interface = arguments[i];
ExpandedSubBlockStart.gifContractedSubBlock.gif        
if(interface.constructor !== Interface) {
            
throw new Error("从第2个以上的参数必须为接口实例.");
        }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++{
            
var method = interface.methods[j][0];
ExpandedSubBlockStart.gifContractedSubBlock.gif            
if(!object[method] || typeof object[method] !== 'function' || object[method].getParameters().length != interface.methods[j][1]) {
                
throw new Error("接口的实现对象不能执行" + interface.name + "的接口方法" + method + ",因为它找不到或者不匹配.");
            }

        }

    }

}


ExpandedBlockStart.gifContractedBlock.gifFunction.prototype.getParameters 
= function() 

    
var str = this.toString();
    
var paramString = str.slice(str.indexOf('('+ 1, str.indexOf(')')).replace(/\s*/g,'');     //取得参数字符串
    try
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
return (paramString.length == 0 ? [] : paramString.split(','));
    }

    
catch(err)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
throw new Error("函数不合法!");
    }

}
 

好了该创建一个html页面来试试效果了:

< script  type ="text/javascript" >
function  test()
{
    
var  man  =   new  Man();
    man.setName(
" Leepy " );
    alert(man.getName());
}
</ script >  

< input  type ="button"  value ="click"  onclick ="test();"   />

最终结果为:"Leepy"的弹出框;

这里还有一点要强调,如果接口上的方法没有在继承类上得到完全实现,或者方法参数个数不匹配,那么就会提示错误;

3. 如果我要一个类继承于另一个类该怎么做呢,继续看例子,这里我再定义一个SchoolBoy(男学生)类:

function  SchoolBoy(classNo, post)
{
    Man.call(
this );
    
this ._chassNo  =  classNo;
    
this ._post  =  post;
}
SchoolBoy.prototype 
=   new  Man();
SchoolBoy.prototype.getName 
=   function () {
    
return   " Mr  "   +   this .name;
}
SchoolBoy.prototype.setName 
=   function (name) {
    
this .name  =  name  +   " 's " ;
}

其中Man.call(this);实际上是将Man中的关键字this赋值于SchoolBoy对象中去,那么SchoolBoy就拥有了Man构造函数中的name属性了;

SchoolBoy.prototype = new Man();实际上是把Man的prototype赋值给SchoolBoy.prototype,那么SchoolBoy就有了Man类中的方法;

而后面跟着的getName(),setName(),实际上是覆盖了前面继承于Man类中的方法了;

然后看看效果:

var  schoolboy  =   new  SchoolBoy( " 三年二班 " " 班长 " );
schoolboy.setName(
" 周杰伦 " );
alert(schoolboy.getName());

最后结果为:"Mr 周杰伦's"的弹出框;

总结

该篇文章主要讲述一些Javascript面向对象的基础以及接口和继承类的实现等等;下一篇将开始真正的Javascript设计模式征程,并且我会结合一些网站系统上的实例来讲;

本篇到此为止,谢谢大家阅读

 

本系列文章转载时请注明出处,谢谢合作!

 相关系列文章:
Javascript乱弹设计模式系列(6) - 单件模式(Singleton)
Javascript乱弹设计模式系列(5) - 命令模式(Command)
Javascript乱弹设计模式系列(4) - 组合模式(Composite)
Javascript乱弹设计模式系列(3) - 装饰者模式(Decorator)
Javascript乱弹设计模式系列(2) - 抽象工厂以及工厂方法模式(Factory)
Javascript乱弹设计模式系列(1) - 观察者模式(Observer)
Javascript乱弹设计模式系列(0) - 面向对象基础以及接口和继承类的实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值