• 元编程之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来模拟发消息的过程。

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

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

  • 相关阅读:
    洛谷P3003 [USACO10DEC]苹果交货Apple Delivery
    洛谷P1576 最小花费
    洛谷P1821 [USACO07FEB]银牛派对Silver Cow Party
    洛谷P1948 [USACO08JAN]电话线Telephone Lines
    洛谷P3371【模板】单源最短路径
    洛谷P2384最短路
    FirstOfAll
    Proxy模式:管理第三方API
    Abstract Server模式,Adapter模式和Bridge模式
    Observer模式
  • 原文地址:https://www.cnblogs.com/liuyanlong/p/3102161.html
Copyright © 2020-2023  润新知