直播开始:'云榨汁机'诞生记--聊聊JavaScript中的'业务建模'

闭包是JavaScript中的一个重要特性,在之前的博文中,我们说闭包是一个'看似简单,其实很有内涵'的特性。当我们用JavaScript来实现相对复杂的业务建模时,我们可以如何利用'闭包'这个特性呢?JavaScript中的'原型继承',又可以解决业务建模中的哪些问题呢?今天我们就通过一家'榨汁机工厂'生产设计'榨汁机'的故事,来聊一聊'闭包'和'原型继承'在业务建模中的作用。
现在直播开始:

1》 工厂默认选用A型刀头方案制造榨汁机

例子当中我们主要涉及到2个函数:
1.榨汁机的生产工厂(JuiceMachineFactory)。
2.生产榨汁机的方案(createJuiceMachineGuide)。
在JuiceMachineFactory中,通过向createJuiceMachineGuide传入参数,可以获得能生产特定刀头的榨汁机的方法_createJuiceMachine,而通过执行_createJuiceMachine,则可以生产出特定刀头的榨汁机。对外表现出来的逻辑就是:JuiceMachineFactory可以确定生产出来的榨汁机所使用的刀头,并且能够生产榨汁机。
用代码表示如下:

var JuiceMachineFactory = function(){
    var newFactory = {};
    var _cutterFn = function(){
        //榨汁机工厂当前使用的榨汁方法,相当于定义了一个特定的刀头
        console.log( '正在使用:A型刀头。' );
    };
    
    //获得一种制造榨汁机的方法
    var _createJuiceMachine = createJuiceMachineGuide( _cutterFn );
    
    //制造了一台榨汁机
    newFactory.createMachine = function(){
        return _createJuiceMachine();
    }

    return newFactory;
        
};

var createJuiceMachineGuide = function( cutterFn ){
    return function(){
        var newMachine = {};
        var _cutterInMachine = cutterFn ;   //用传入的'刀头'作为制造榨汁机的原件
        
        newMachine.makeJuice = function(){
            //开始榨汁
            console.log( '##开始榨汁:' );
            //调用传入的私有变量
            _cutterInMachine();
        }
        
        return newMachine ;
    }
}
//先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var  machine_A = factory.createMachine();
//使用这台榨汁机
machine_A.makeJuice( );

运行代码之后输出如下结果:

##开始榨汁:
正在使用:A型刀头。

【分析】
1. 和我们预期的一样,我们通过传入_cutterFn来获得一个'采用刀头A来进行榨汁'的榨汁机制造方案,并且用这个方案制造了一台榨汁机。
2. 在使用新造的这台榨汁机进行榨汁的时候,确实是使用了刀头A的方法。
>> 通俗地讲,工厂制造这台榨汁机时,选用了哪种'制造方案',决定了生产出来的榨汁机的特性

2》工厂选用了B型刀头方案制造榨汁机
经过一段时间的发展,市场人员反馈说,现在用户都希望使用B型的刀头,我们得修改制造一下制造榨汁机的方案了。但是,从目前来看,我们并没有预留'修改榨汁机刀头'的接口,所以需要调整一下工厂,使它能够方便选择采用哪种刀头方案,调整后的代码如下:

var JuiceMachineFactory = function(){
    var newFactory = {};
    var _cutterFn = function(){
        //榨汁机工厂当前使用的榨汁方法,相当于定义了一个特定的刀头
        console.log( '正在使用:A型刀头。' );
    };
    
    //获得一种制造榨汁机的方法
    var _createJuiceMachine = createJuiceMachineGuide( _cutterFn );
    
    //制造了一台榨汁机
    newFactory.createMachine = function(){
        return _createJuiceMachine();
    }
    //修改新的制造榨汁机的方案
    newFactory.setCutterFn = function( new_fn ){
        _cutterFn = new_fn ;
        return true;
    }

return newFactory;
        
};

var createJuiceMachineGuide = function( cutterFn ){
    return function(){
        var newMachine = {};
        var _cutterInMachine = cutterFn ;   //用传入的'刀头'作为制造榨汁机的原件
        
        newMachine.makeJuice = function(){
            //开始榨汁
            console.log( '##开始榨汁:' );
            //调用传入的私有变量
            _cutterInMachine();
        }
        
        return newMachine ;
    }
}
//先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var  machine_A = factory.createMachine();
//使用这台榨汁机
machine_A.makeJuice( );

//设置一下工厂的制造方案
factory.setCutterFn( function(){
    console.log( '正在使用:B型刀头。' );
});
//用新的方案再制造一台榨汁机
var machine_B = factory.createMachine();
machine_B.makeJuice( );

运行代码之后输出如下结果:

##开始榨汁:
正在使用:A型刀头。
##开始榨汁:
正在使用:A型刀头。

【分析】
1. 采用默认的工艺,制造出来的榨汁机,采用的是 A型刀头。
2. 后面通过setCutterFn( ) 调整了制造方案,生产出来的榨汁机,按理,应该是采用“B型刀头”了啊!为什么还是A型刀头?!

这个问题引出了JavaScript的一个知识点:JavaScript中的对象的赋值,是一种'引用'传递。
我们分析一下整个过程。

1. 下面的这段代码执行之后,变量_cutterFn引用了一个'函数对象',我们给这个函数对象一个编号:F_007。

var _cutterFn = function(){
  //榨汁机工厂当前使用的榨汁方法,相当于定义了一个特定的刀头
  console.log( '正在使用:A型刀头。' );
};

示意图如下:

2.  执行下面的这段代码之后,相当于变量_cutterFn 和 _cutterInMachine 都指向了 F007 这个函数对象。

var _createJuiceMachine = createJuiceMachineGuide( _cutterFn ); 

示意图如下:

3. 当我们向用新的方案来制造B型刀头榨汁机,在前面的代码中,我们用了setMakeMachineMethod方法,代码如下:

//设置一下工厂的制造方案
factory.setMakeMachineMethod( function(){
    console.log( '正在使用:B型刀头。' );
});

此时各个变量所指的对象如下所示:

因为只是赋值仅仅是变量之间的'引用'传递,所以,执行完毕之后,变量_cutterFn 和 _cutterInMachine 并没有必然的联系!也就是说,_cutterFn 指向了别的地方(代号为F008的新对象),_cutterInMachine 并不会随之改变,除非再来一次赋值。

在我们的例子,我们要改变的其实不是_cutterFn所指的对象,我们应该改变 _cutterInMachine 所指的对象。也就是要重新'赋一次'值,生成一个新的方案_createJuiceMachine。调整后的代码如下:

var JuiceMachineFactory = function(){
    var newFactory = {};
    var _cutterFn = function(){
        console.log( '正在使用:A型刀头。' );
    };
    
    //获得一种制造榨汁机的方法
    var _createJuiceMachine = createJuiceMachineGuide( _cutterFn );
    
    //制造了一台榨汁机
    newFactory.createMachine = function(){
        return _createJuiceMachine();
    }
    //修改新的制造榨汁机的方案
    newFactory.setMakeMachineMethod = function( new_fn ){
        _cutterFn = new_fn ;
        //关键是修改下面的内容
        _createJuiceMachine = createJuiceMachineGuide( _cutterFn );
        return true;
    }

    return newFactory;
        
};

var createJuiceMachineGuide = function( cutterFn ){
    return function(){
        var newMachine = {};
        var _cutterInMachine = cutterFn ;   //用传入的'刀头'作为制造榨汁机的原件
        
        newMachine.makeJuice = function(){
            //开始榨汁
            console.log( '##开始榨汁:' );
            //调用传入的私有变量
            _cutterInMachine();
        }
        
        return newMachine ;
    }
}
//先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var  machine_A = factory.createMachine();
//使用这台榨汁机
machine_A.makeJuice( );

//设置一下工厂的制造方案
factory.setMakeMachineMethod( function(){
    console.log( '正在使用:B型刀头。' );
});
//用新的方案再制造一台榨汁机
var machine_B = factory.createMachine();
machine_B.makeJuice( );

//再看一下原来制造的那台machine_A,是否还是使用了A型刀头?
//答案是还是A型刀头,因为每执行一次createJuiceMachineGuide,其实是新增一个闭包!
//也就是说,
//制造machine_A的那套方案,并没有消失,它的榨汁的方法,还是指向F007那个函数对象。
//而制造machine_B的那套方案,榨汁方法指向了F008这个函数对象。
machine_A.makeJuice( );

运行代码之后输出如下结果:

##开始榨汁:
正在使用:A型刀头。
##开始榨汁:
正在使用:B型刀头。
##开始榨汁:
正在使用:A型刀头。

这回就正确了!
在生成对象machine_A和machine_B的时候,实际上我们调用了两次createJuiceMachineGuide,生成了两个独立的'运行时环境',而在这两次调用的过程中,运行时环境中的_cutterInMachine私有变量所指向的对象是不一样的。
整个过程示意如下:

 

【小节】
1. JavaScript中的赋值、参数传递都是'引用'传递。
    "这个好理解不?",
    "好理解。"
2. 执行一次'闭包'函数,会生成一个独立的'运行时环境'。
   "这个好理解不?"
   "好理解哈,相当于返回一个对象,这个对象中的函数可以访问'闭包'函数中的私有成员。"
把它们放到一起综合起来理解,就可以很好的解释"工厂可以决定生产出来的榨汁机采用哪种'刀头'了。"

2》给榨汁机添加'水果'

截止目前为止,我们在'榨汁机'这个模型中,其实还仅仅定义了'榨汁方法',并且确定,采用哪种榨汁方法(选用哪种刀头),是在工厂设置'制造方案'是决定的。但是,完整的榨汁过程,应该是有'水果'的,并且,'水果'应该是可以替换的。

用代码描述如下:

var JuiceMachineFactory = function(){
    var newFactory = {};
    var _cutterFn = function(){
        console.log( '正在使用:A型刀头。' );
    };
    
    //获得一种制造榨汁机的方法
    var _createJuiceMachine = createJuiceMachineGuide( _cutterFn );
    
    //制造了一台榨汁机
    newFactory.createMachine = function(){
        return _createJuiceMachine();
    }
    //修改新的制造榨汁机的方案
    newFactory.setMakeMachineMethod = function( new_fn ){
        _cutterFn = new_fn ;
        //关键是修改下面的内容
        _createJuiceMachine = createJuiceMachineGuide( _cutterFn );
        return true;
    }

    return newFactory;
        
};

var createJuiceMachineGuide = function( cutterFn ){
    return function(){
        var newMachine = {};
        var _cutterInMachine = cutterFn ;   //用传入的'刀头'作为制造榨汁机的原件
        var _fruit = '橙子' ;               //默认是橙子汁

newMachine.selectFruit = function( new_fruit ){ _fruit = new_fruit ; } newMachine.makeJuice = function( ){ //开始榨汁 console.log( '##开始榨汁:' ); console.log( '使用水果<' +_fruit+'>' ); //调用传入的私有变量 _cutterInMachine(); } return newMachine ; } } //先创建唯一的一座工厂: var factory = JuiceMachineFactory(); //现在,我们生产一台榨汁机: var machine_A = factory.createMachine(); //使用这台榨汁机 machine_A.makeJuice( ); //选择另外的水果 machine_A.selectFruit( '西瓜' ); //再次榨水果 machine_A.makeJuice( );

运行代码之后输出如下结果:

##开始榨汁:
使用水果<橙子>
正在使用:A型刀头。
##开始榨汁:
使用水果<西瓜>
正在使用:A型刀头。

【分析】
与我们预期的一样:默认情况下,我们使用<橙子>来榨果汁,因为我们也开放了操纵_fruit的接口,所以,我们也可以使用<西瓜>来榨果汁。

【小节】
到目前为止,我们掌握了以下两个情况的处理:
1. 在工厂中可以根据需要调整制造方案:

我们通过传入参数的方式,结合闭包的特性,更换_createJuiceMachine的值,用另外一个'闭包'的运行结果代替之前的'闭包运行结果'。
由于两个运行结果中的_cutterInMachine的值不同,相当于产生了两套不同的制造榨汁机的方法。
>>
回到举例的场景中:制造榨汁机的工厂,可以决定生产出来的榨汁机采用哪种'刀头'。
对于JavaScript本身,由于'高阶函数'的特点,相当于通过传入参数,可以定制'闭包'函数本身。
举例中:
         createJuiceMachineGuide:制造'闭包'函数的函数。
         _createJuiceMachine:就是执行createJuiceMachineGuide之后返回的'闭包'函数。
         var machine_A = factory.createMachine();:machine_A,执行_createJuiceMachine之后的返回的一个'闭包'。
相关知识点:闭包,高阶函数特性,JavaScript赋值操作的本质。

2. 在榨汁时,可以设定榨汁采用的'水果'。
这相当于给了我们一个从'业务模型'认识'闭包'函数的好例子:
a. 运行'闭包'函数,会产生一个'对象'(或函数定义)。这个'对象'中,原来定义在'闭包'函数中的局部变量和参数,成为这个'对象'的私有成员。而这个'对象'中的方法,则成为'对象'的对外接口(公共函数),这些对外接口可以访问'对象'的私有成员。当然,一般不会在返回的'对象'中直接设置'属性'成员,因为那样是'违背'封装性的原则的。
b. 设计一个'闭包'函数的时候:
1> 如果某个成员是业务对象的属性(例如:_fruit),那么,就把它声明为'闭包'函数的局部变量或通过参数传入。
2> 如果某个函数并不想开放给别人使用(例如:_cutterInMachine),那么也把它声明为'闭包'函数的局部变量或通过参数传入。
3> 业务对象对外呈现出来的功能(例如:makeJuice),则把它声明为该对象的'成员'函数。
4> '闭包'函数中的'私有成员',可以通过对外开放出来的公共函数(makeJuice和selectFruit)更改。
讨论到这里,借助'闭包'和'高阶函数'的特性,我们似乎已经能够实现应用中的'业务'建模。我们定义好一个'闭包函数'之后,就可以'产生满足我们业务要求'的对象,并且,这些对象具有很好的'封装性'。
到现在为止,我们还没有用到this,没有this的日子,似乎也是很美好的。

3》给榨汁机增加云端系统

现在的社会,当我们讲到家电设备时,我们往往会说‘智能家电’,取名称时喜欢带一个'云'字,例如:云冰箱,云电视。其实质就是,每个家电背后都有一个'云端'系统,通过'云端'系统,我们可以'远程'与我们的家电通信。比如,还没有下班,我们知道今天下午有一场AC米兰的球赛。这时候,借助'云端'系统,我们可以通过手机遥控家里的'云电视',让它把AC米兰的球赛先录下来,这样,我们下班回到家就可以观看。
现在,我们要给榨汁机也增加这样的'云端'系统,这样,我们就可以在下班回家的路上远程操控榨汁机,一回到家,就能喝到'新鲜'的果汁。比起那些只有'定时'功能的家电,'云家电'还是要方便很多,比如:你如果发现路上堵车了,就可以晚点启动'榨汁作业'。如果老板告诉你晚上要加一下班,你就可以不启动'榨汁作业'。
好了,现在我们就给榨汁机增加一个'云控制系统',然后增加一个'远程启动'的功能。

var JuiceMachineFactory = function(){
    var newFactory = {};
    var _cutterFn = function(){
        console.log( '正在使用:A型刀头。' );
    };
    
    //获得一种制造榨汁机的方法
    var _createJuiceMachine = createJuiceMachineGuide( _cutterFn );
    
    //制造了一台榨汁机
    newFactory.createMachine = function(){
        return _createJuiceMachine();
    }
    //修改新的制造榨汁机的方案
    newFactory.setMakeMachineMethod = function( new_fn ){
        _cutterFn = new_fn ;
        //关键是修改下面的内容
        _createJuiceMachine = createJuiceMachineGuide( _cutterFn );
        return true;
    }

    return newFactory;
        
};

var createJuiceMachineGuide = function( cutterFn ){
    return function(){
        var newMachine = {};
        var _cutterInMachine = cutterFn ;   //用传入的'刀头'作为制造榨汁机的原件
        var _fruit = '橙子' ;               //默认是橙子汁
        
        var _cloudCenter = {
            remoteStart:function(){
                //这里模拟执行远程启动时,云端控制'榨汁机'开始榨汁
                console.log( '^^云中心收到指令,马上控制榨汁机开始榨汁^^' );
                newMachine.makeJuice();
                return ;
            }
        }
        
        newMachine.selectFruit = function( new_fruit ){
            _fruit = new_fruit ;
        }
        
        newMachine.makeJuice = function( ){
            //开始榨汁
            console.log( '##开始榨汁:' );
            console.log( '使用水果<' +_fruit+'>' );
            //调用传入的私有变量
            _cutterInMachine();
        }
        
        //增加远程启动榨汁机的功能
        newMachine.remoteStart = function(){
            //模拟这个功能要借助云端中心执行
            _cloudCenter.remoteStart();
        }

        return newMachine ;
    }
}

//先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var  machine_A = factory.createMachine();
//使用这台榨汁机
machine_A.remoteStart( );

运行代码之后输出如下结果:

^^云中心收到指令,马上控制榨汁机开始榨汁^^
##开始榨汁:
使用水果<橙子>
正在使用:A型刀头。

【分析】
嘿嘿,看来增加一个云端的系统也是很简单的嘛。没错,这样的设计目前来看是符合我们的预期的。但是,我们是一个'榨汁机'的工厂,这就意味着我们不是'仅仅'生产一台'榨汁机',我们可能会生产上千台,上万台这样的榨汁机。这样,我们再次从'空间'分配的角度来剖析整个榨汁机的过程。
运行下面的代码:

var  machine_A = factory.createMachine();

相当于执行了_createJuiceMachine这样的闭包函数,我们知道:"每执行一次'闭包'函数,JavaScript引擎会为闭包函数中的'局部变量'分配一次空间。"
执行1次'闭包'函数factory.createMachine()之后和执行n次'闭包'函数之后的花费空间示意:

假设_cloudCenter这个局部变量占据了很大的空间,我们用深颜色标识出来。回到业务场景,搭建一个'云中心'需要大量的资金投入,但是,我们现在的搞法就相当于:
"为生产出来的每一台榨汁机,都搭建一个专属的云中心。"
显然,这是不合理的。

有同学可能会说,对于_cloudCenter这个局部变量,我们没有必要在'闭包'函数的函数体内专门全新生成一个对象,可以引用'外部'的一个唯一对象,这样就可以节省大量的空间。

这种方式确实也是一种解决方案,在之前的'地址选择控件开发'这篇博文中,针对全国地址信息模型这个比较大的对象,我们就是采用这种方式。

我们再仔细审视一下_createJuiceMachine这个闭包函数,除了_fruit之外,似乎其他的方法也都很_cloudCenter一样,没有必要每生成一个对象,就全新打造一份!
综合上面的分析,我们可以使用JavaScript的'原型继承'特性,来完成我们的业务目标。
我们先来看一下,优化之前,machine_A, machine_B......这些对象的'原型链'关系。

如果基于'原型继承'来实现我们的业务目标,那么,我们希望我们的'原型链'关系会变成如下所示:

有一个BaseMachine,具有我们'期望'的功能。新创建的machine_A, machine_B都'原型继承'自BaseMachine。

下面,我们用代码来实现这样的业务:

var JuiceMachineFactory = function(){
    var newFactory = {};
    var _cutterFn = function(){
        console.log( '正在使用:A型刀头。' );
    };
    
    //获得一种制造榨汁机的方法
    var _createJuiceMachine = createJuiceMachineGuide( _cutterFn );
    
    //先制造一个'原型对象'
    var _base_machine = _createJuiceMachine();
    
    //制造了一台榨汁机
    newFactory.createMachine = function(){
        //基于'原型'对象来创建新的榨汁机
        var new_machine = Object.create( _base_machine );
        new_machine.setDefaultFruit();
        return new_machine;
    }
    return newFactory;
        
};

var createJuiceMachineGuide = function( cutterFn ){
    return function(){
        var newMachine = {};
        var _cutterInMachine = cutterFn ;   //用传入的'刀头'作为制造榨汁机的原件
        
        var _fruit = '橙子' ;               //默认是橙子汁
        
        var _cloudCenter = {
            remoteStart:function(){
                //这里模拟执行远程启动时,云端控制'榨汁机'开始榨汁
                console.log( '^^云中心收到指令,马上控制榨汁机开始榨汁^^' );
                //注意:因为在makeJuice中使用了this.fruit,
                //所以,这里也要用this来调用,否则,makeJuice中的this所指对象就不正确!
                this.makeJuice();
                return ;
            }
        }
        
        newMachine.selectFruit = function( new_fruit ){
            this.fruit = new_fruit ;
        }
        
        //看一下当前使用的苹果
        newMachine.showFruit = function( ){
            console.log( '>>正在使用的水果是:' + this.fruit );
            return ;
        }
        
        newMachine.setDefaultFruit = function(){
            this.fruit = _fruit;       //设置新的对象使用默认的水果
        }
        
        newMachine.makeJuice = function( ){
            //开始榨汁
            console.log( '##开始榨汁:' );
            console.log( '使用水果<' +this.fruit+'>' );
            //调用传入的私有变量
            _cutterInMachine();
        }
        
        //增加远程启动榨汁机的功能
        newMachine.remoteStart = function(){
            //模拟这个功能要'借助'云端中心执行
            //相当于要'借用'_cloudCenter.remoteStart这个函数,借用函数怎么搞,用apply哈
            _cloudCenter.remoteStart.apply( this , arguments );
        }
        return newMachine ;
    }
}

//先创建唯一的一座工厂:
var factory = JuiceMachineFactory();
//现在,我们生产一台榨汁机:
var  machine_A = factory.createMachine();
//设置一下水果的值
machine_A.selectFruit( '苹果' );
//查看一下当前所使用的水果
machine_A.showFruit( );
//使用这台榨汁机
machine_A.remoteStart( );

//再全新生成一个对象machine_B
//
var machine_B = factory.createMachine();
machine_B.makeJuice( ); 

运行代码之后输出如下结果:

>>正在使用的水果是:苹果
^^云中心收到指令,马上控制榨汁机开始榨汁^^
##开始榨汁:
使用水果<苹果>
正在使用:A型刀头。
##开始榨汁:
使用水果<橙子>
正在使用:A型刀头。

【分析】
就是为了使用'原型继承'这个特性,我们引入了this,为了达到各种预期的效果,我们对各个函数都进行了调整。
1. 工厂中,生成machine对象的方式,调整成:先生成一个原型对象(_base_machine),然后基于这个原型对象创建全新的对象
2. 基于原型对象创建的全新对象,自己是没有任何'成员属性'的。
所以,我们通过调用new_machine.setDefaultFruit();来为新创建的对象添加自己的成员属性fruit,而它的值就是原型对象(_base_machine)中闭包空间中的值_fruit,这样符合我们的业务预期。
3. 将原型对象中,对外公开方法中_fruit的地方,都换成了this.fruit,因为我们希望,当我们使用原型对象_base_machine中的对外公开方法时,其中的fruit应该指代全新创建的对象的fruit成员,而不是生成_base_machine时,闭包中存在的_fruit。
4. 原型对象的对外公开方法(remoteStart),其实调用的不是原型对象本身拥有的方法,而是'借用'了'第三方对象'_cloudCenter中的方法。如果不做调整,直接调用_cloudCenter.remoteStart();那么,函数体的所指代的this就会指向了_cloudCenter。如何向别的对象'借用'方法,我们在前面的博客'闲聊JS中的apply和call'中有比较详细的介绍,所以,我们采用如下的方式调用:

_cloudCenter.remoteStart.apply( this , arguments );

这样,就确保了当你执行new_machine.remoteStart()时,_cloudCenter.remoteStart函数体中的this也是指代new_machine,与我们的业务预期相符。

经过一番周折,我们终于利用JavaScript的'原型继承'特性,实现了我们的'业务目标'。JavaScript的函数调用特性,使得JavaScript更具有'弹性',当然,也使实现逻辑变得更加复杂。当你'迫不得已'要在函数体中使用this的时候,你一定要想清楚,到时候真正调用这个函数的对象会是谁,它具有什么样的成员属性。
回顾整个过程,下面这个语句,其实是用到了一定的随意性,或者说是JavaScript语言的'弹性'。

newMachine.setDefaultFruit = function(){
    this.fruit = _fruit;       //设置新的对象使用默认的水果
}

如果不存在,那么就给对象新增一个fruit成员属性。不过问题不大,我们把这个随意性控制在selectFruit这个方法当中了。现在再回到第2个场景"工厂选用了B型刀头方案制造榨汁机",因为我们使用了'原型继承',要换一种榨汁方式,就需要修改原型对象中'运行时环境'中的_cutterInMachine变量。到底要如何修改呢?就留作练习吧。

【总结】

今天我们以'榨汁机工厂'生产特定功能的'榨汁机'为场景,讲解了如何用JavaScript的'闭包'特性进行'业务建模'。首先我们采用了更改'_createJuiceMachine'的方式达到更换榨汁方式的目的。后来,我们给生产的'榨汁机'增加了'云'功能。当增加了'云'功能之后,发现从'空间利用'的角度看,原来的生产'榨汁机'的方式存在一些问题。最后,我们利用了JavaScript的'原型继承'特性,解决了'空间利用'问题。'榨汁机'只是我们讲故事的道具,真正想表达的是JavaScript的各种特性。
下次如果有时间,我们一起聊聊JavaScript社区中的设计模式。这里先举一个小故事。
有一天,老板对小明说,“小明啊,周末我们超市苹果打特价,6.98一斤,你画一张海报宣传一下。”于是,小明设计了下面的一张海报:

老板说,“设计得不错,果然身手不凡哈!这样,你再画1000份,明天周五给你放一天假,你去东门路口把画好的1000份宣传单发了。”
"再画1000份?!"
现在已经是周四下午5点,你觉得小明是会拿起画笔赶紧开始画呢?还是拿起画笔赶紧开始画呢?当然不会啦,小明肯定会选择拿着之前画好的宣传海报,去扫描复印1000份。

生活中会遇到许多的'迫不得已',所以,我们要学会聪明做事,善待自己。只有聪明地做事,才能使用户满意,老板开心,自己也不闹心。而对于我们写代码的人来说,了解一些'设计模式',也许能使自己做到:聪明地做事情,优雅地写代码,从容面对各种'迫不得已'。
感谢诸位捧场,谢谢。

原文地址:https://www.cnblogs.com/alai88/p/5544522.html