元编程之javascript

最近拜读了下ruby元编程,对元编程编程触动很深。本人一直从事前端开发工作,后来反思了一下javascript在元编程方面的能力。

相信大家对元编程多少有些了解,元编程简单说就是“编写代码的代码”,换个高雅解释即是元编程是编写在运行时操纵语言构件的代码”。

反射用元编程解释就是,一门语言拥有对自身的元编程能力就表现在反射。

Demo短小,所以没多加过多的业务注释,毕竟代码很短,不是想告诉解决某一特殊的模式问题。只是想传达一种编程范式,不必深究其中的业务逻辑。

下面的demo问题模型如下:

    “一个部门有很多例如电脑之类的硬件资源,这些硬件需要IT部门管理,

     每台电脑又由很多零件组成(显卡,声卡,CPU,鼠标,键盘......),

     这个电脑财产的管理软件维护工作在你的手上,不过这个第一版不是你开发的,

     IT部门对你维护的此款管理硬件管理系统提出了一个新的需求,

      要求电脑组件价格超过100刀乐的要在输出前加上‘*’  ”

下面这个DS类是核心的底层接口类。你也可以叫它model层。封装了底层关于电脑的数据库。总之这个是别人给你提供的。你要做的就是在这个封装了数据信息的接口基础上做上层开发。

function DS( computorId ){

this
.computorId = computorId; //不同的电脑有不同的配置。 this.data = {
mouseInfo :
"鼠标",
mousePrice :
"999",
keyboardinfo :
"键盘",
keyboardprice :
"888", lcdInfo : "驱动", lcdPrice : "888" /* *略去其他音响,声卡,先卡等属性 */ } } //DS 您可以当做数据库系统,或者前端开发过程中你也可以当做后台返回的json数据。 DS.prototype = {
constructor : DS,
get_mouse_info :
function() { return this.data.mouseInfo; }, //取回鼠标信息
get_mouse_price : function() { return this.data.mousePrice; }, //取回鼠标价格
get_keyboard_info : function() { return this.data.keyboardinfo; },//取回键盘信息

get_keyboard_price : function() { return this.data.keyboardprice; }//取回键盘信息 /* *还有其他一些关于显示器,音响,声卡等等信息和价格 */ }

 

下面让我们看看最直观的方式

function Computer( id, data_source ){
    this.id = id;
    this.data_source = data_source;
}

//没有重构之前的源代码,这种写法中规中矩,初级程序员都会直观想到这种方式。作为大牛的你一定不会这么平庸的写代码
Computer.prototype = {
    mouse : function() {
        var info = this.data_source.get_mouse_info( this.id ),
            price = this.data_source.get_mouse_price( this.id ),
            result = "mouse:" + info + price;
        price >= 100? return "*" + result : return result;
    }
    keyboard : function() {
        var info = this.data_source.get_keyboard_info( this.id ),
            price = this.data_source.get_keyboard_price( this.id ),
            result = "mouse:" + info + price;
        price >= 100? return "*" + result : return result;
    }

    /*

   此处略去其他显示器,音响之类的信息。

   */
}
var zs = new
Computer('2834750234', new DS(2342341244));

从上面的代码我们能直观分析出来,这种写法重复性很强,很多重复工作。典型硬编码。电脑有多少设备就需要手动的定义多少种取回设备信息价格的方法。可扩展性和维护性极差。

 

 

//第一次改进-动态派发。

function Computer( id, data_source ) {
    this.id = id; //电脑的Id信息
    this.data_source = data_source; //电脑的组件信息
}
Computer.prototype = {
    mouse : function() {
        return this.component( 'mouse' );
    },
    keyboard : function() {
        return this.component( 'keyboard' ); //动态的调用方法,只需传递方法的字符串参数。
    },

    //电脑其他显示器,音响之类的省略,如果问题模型的组件越多,此处的重复也很多,不过较之第一种,已经优化了许多。
    component : function( name ) {
        var methodName = 'get_' + name,
            info = this.data_source[ methodName + '_info' ]( this.id ), //我们首先要取出关于电脑的一些组件info
            price = this.data_source[ methodName + '_price' ]( this.id ),//其次我们要取出关于电脑的一些price
            result = "mouse:" + info + price;
            reuslt = price >= 100? "*" + result : result; //判断价格,高于100块的加上'*'
        return reuslt;
    }
}

var obj = new DS( '15' );
var comObj = new Computer( 12, obj );
console.log(comObj.keyboard());

 

我们看到较之第一种已经优化了很多,重复性工作也变少了,动态派发的小技巧全在javascript对象方法的动态调用。虽说抽象出通用层,但是还是避免不了硬编码,可维护性也较差。

 

 

 

进一步改进-动态创建方法,对于第二种方法,我们还是无法满足,毕竟重复工作还是占了很大一部分。作为一个大牛的你一定不会写出这种中级程序员的代码,于是你像高级程序员做法发起挑战

var Computer = function() {
var
AimClass = function( id, data_source ) { this.id = id; this.data_source = data_source; } //第三种写法在于动态的创建方法,动态创建方法?你没听错,就是代码执行中创建方法,这要谢谢我们伟大的new Function(),以前对new Function着实无法理解,谁会这么定义一个function。 AimClass.define_component = function( name ) { var name = name, fnBody = 'var methodName = "get_' + name + '",' + 'info = this.data_source[ methodName + "_info" ]( this.id ),' + 'price = this.data_source[ methodName + "_price" ]( this.id ),' + 'result = "mouse:" + info + price;' + 'reuslt = price >= 100? "*" + result : result;' + 'return reuslt;' this.prototype[ name ] = new Function( 'name', fnBody );//此处是重点,动态创建方法(包括get_*_info,get_*_price);你再也不用手动的去定义那些讨厌的方法了。
return this; } AimClass.define_component( 'mouse' ) //不过在此你还是要调用下你的类方法 .define_component( 'keyboard' );//只需你把方法参数写进去就可以 //这里还有很多关于显示器,音响之类的。从这里可以看出虽然第三种照第一,第二种优化了很多重复工作。可还是觉得还是需要调用很多这个创建实例方法的类方法 return AimClass; }() var obj = new DS( '15' ); var comObj = new Computer( 12, obj ); console.log(comObj.keyboard());

动态定义方法 相比 动态派发 有了进一步的优化,但是这种优化不是颠覆性的。这里你还需要手动的过程即“AimClass.define_component()”。

 

 

改进之最后一步(内省方式进一步优化代码),终于我们来到终极改造,也是你作为大牛应该一展身手之处

var Computer = function( allMethod ){
    var aimClass = function( id, data_source ) {
        this.id = id;
        this.data_source = data_source;
    }
    AimClass.define_component = function( name ) {
        var name = name,
            fnBody = 'var methodName = "get_' + name + '",' +
                     'info = this.data_source[ methodName + "_info" ]( this.id ),' +
                     'price = this.data_source[ methodName + "_price" ]( this.id ),' +
                     'result = "mouse:" + info + price;' + 
                     'reuslt = price >= 100? "*" + result : result;' +
                     'return reuslt;'
        this.prototype[ name ] = new Function( 'name', fnBody );
    }
    for( var i in allMethod ) { //javascript对象内省机制。这种机制,解放了你的双手。不需要重复工作。不需要重复调用动态创建实例方法的类方法
        var reg = /^get_(.+)_info$/, str = '';
        if ( allMethod.hasOwnProperty( i ) && ( ( typeof allMethod[ i ] ) == 'function' ) && ( i != 'constructor' ) ) {
            str = i.replace( reg, '$1' );
            AimClass.define_component( str );
        }
    }
    return AimClass;
}( DS.prototype ) //把DS的所有方法穿入当参数。
var obj = new DS( '15' );
var comObj = new Computer( 12, obj );
console.log( comObj );
console.log( comObj.keyboard() );

最后的代码我们看到运用点正则的技巧还有对js对象(DS.prototype)内省机制。

 

我们看之间耍的小把戏已经让一个冗余的代码变得可维护性很强。

下面我们来看另外一种奇淫巧计。

由于javascript没有methodmissing这样迷人的内核方法。我自己模拟一个,当然这种模拟是有缺陷的。缺陷就是方法的调用是间接调用。而且模拟的方法不是内核方法。

var AimClass = function( id, data_source ) {
        this.id = id;
        this.data_source = data_source;
}
AimClass.prototype = {
    constructor: AimClass,

    methodmissing: function( name, args ) {
        var methodName = 'get_' + name;
        if ( !this.data_source[ methodName + '_info' ] ) {
            return '找不到此设备信息'
        }
        var info = this.data_source[ methodName + '_info' ]( this.id ),
price = this.data_source[ methodName + '_price' ]( this.id ),
result = "mouse:" + info + price; reuslt = price >= 100? "*" + result : result; console.log(result);
return this; }, methods: function() { var args = Array.prototype.slice.call( arguments ), methodName = args.shift() || undefined, methodArgs = args.length > 1? args : []; if ( typeof methodName == 'undefined' ) { return; } if( this[ methodName ] ) { return this[ methodName ].apply( this, methodArgs ); } else { return this[ 'methodmissing' ]( methodName, methodArgs ); } } } var b = new AimClass( 12, new DS( '15' ) ); b.methods('keyboard').methods('www');

 对象调用方法,例如obj.fn1();说白了过程不过就是向obj对象发送一条‘fn1’的消息,这里我们用b.methods来模拟发消息的过程。

 以上代码大部分要做的事情是操作语言构件,而并非直接要处理业务逻辑。让代码去管理代码,好比你直接去管理各代码‘士兵’,不如设立一个代码‘将军’。

 

 

鄙人长期处于一线开发工作当中,对于文笔还略有欠缺,程序员交流更多的靠代码。如果觉得赞,请不要惜墨。

posted @ 2013-05-27 19:21 刘艳龙 阅读(...) 评论(...) 编辑 收藏