当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码,这就是开放-封闭原则,使用它最重要的就是找出程序中将要发生变化的地方,然后把变化封装起来,通过封装变化,我们可以把系统中稳定不变的部分和容易变化的部分隔离起来,若以后需要修改则只修改封装好的变化部分的,这样更加易于维护一些。
一般来说,常用两种方法来进行开放-封闭原则代码的编写,放置挂钩和使用回调函数
放置挂钩,即我们在程序有可能发生变化的地方放置一个挂钩,挂钩的返回结果决定了程序的下一步走向,这其实就是封装变化,在容易改变的地方放置了挂钩,这个地方就拥有了分支的可能,如果以后对其进行修改,那么我们预先设置好的挂钩也会对修改后的结果作出响应,这样程序有了更大的灵活性,变通性,而且通过挂钩我们还保持着对整个程序的良好把控,用一句经典的话来说,棋局一下子活了。
使用回调函数,我们通常把一部分易于变化的逻辑封装在回调函数中,然后将其传入一个处理回调函数的稳定和封闭函数中,这样不同的回调函数就可以进入不同的分支中进行不同的变化,整个过程也就是封装变化的过程。
下面给出发布-订阅模式和职责链模式的具体实现
发布-订阅模式又称观察者模式,可以用来降低多个对象之间的依赖关系,一个对象不必再显式调用另外一个对象的某个接口,其将可能变化的监听以及发布函数都进行了良好的封装。下面给出一个具体实现,消费者订阅售楼处消息
var event = {
cilentList:[],
listen: function( key, fn ){
if( !this.clientList[ key ] ){//如果之前没有订阅者订阅这个信息,则新创建一个发布要求数组
this.clientList[ key ] = [];
}
this.clientList[ key ].push( fn );
},
trigger: function(){
var key = Array.prototype.shift.call( arguments ),
fns = this.clientList[ key ];
if( !fns || fns.length === 0 ){//如果此消息没有被人订阅
return false;
}
for( var i = 0, fn; fn = fns[ i++ ]; ){
fn.apply( this, arguments );
}
}
};
var installEvent = function( obj ){
for( var i in event ){
obj[ i ] = event[ i ];
}
};
var salesOffices = {};
installEvent( salesOffices );
salesOffices.listen( 'squareMeter88', function( price ){
console.log( '价格=' + price );
});
salesOffices.listen( 'squareMeter100', function( price ){
console.log( '价格=' + price );
});
salesOffices.trigger( 'squareMeter88', 2000000 );
salesOffices.trigger( 'squareMeter100', 3000000 );
event对象包含着一个售楼处对象应该有的属性,订阅列表,监听函数,发布函数,订阅列表里面有订阅者想要的信息以及对应订阅者的发布要求(函数),订阅信息是键值,各个订阅者的要求是函数数组,监听函数是监听订阅者需求并储存在订阅列表里,发布函数里面则是发布时的具体实现,主要是遍历订阅列表。
职责链模式,使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有个对象处理它为止,各个容易改变的处理请求的链条上都是封装好的函数,若处理请求的方式发生变化只需维护链条某环节或添加某节链条即可。下面给出一个订单处理系统的职责链。
var order500 = function( orderType, pay, stock ){
if( orderType === 1 && pay === true ){
console.log( '500元定金预购,得到100元优惠券' );
}else{
return 'nextSuccessor';
}
};
var order200 = function( orderType, pay, stock ){
if( orderType === 2 && pay === true ){
console.log( '200元定金预购,得到50元优惠券' );
}else{
return 'nextSuccessor';
}
};
var orderNormal = function( orderType, pay, stock ){
if( stock > 0 ){
console.log( '普通购买,无优惠' );
}else{
console.log( '手机库存不足' );
}
};
var Chain = function( fn ){
this.fn = fn;
this.successor = null;
};
Chain.prototype.setNextSuccessor = function( successor ){
return this.successor = successor;
};
Chain.prototype.passRequest = function(){
var ret = this.fn.apply( this, arguments );
if( ret === 'nextSuccessor' ){
return this.successor && this.successor.passRequest.apply( this.successor, arguments );
}
return ret;
};
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );
chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( orderNormal );
在代码的最后我们可以看到3个函数组成的职责链,如果想要继续添加处理方法,则只需定义然后在追加到职责链中间或最后即可,不用修改源代码。
最后需要提及的是,开放-封闭原则虽然是面向对象编程设计模式中非常基本的原则,但是他仍然不是完全要遵守,他只是一个原则是一个建议,实际上,让程序保持完全封闭是不容易做到的,就算在技术上可以做到,那势必也要花费极大地代价,引入很多抽象的概念,形成一些我们难以维护的对象,极大增加代码的冗杂度,更何况有些代码是无论如何都无法完全封闭的。所以我们在实际开发时,要根据环境因地制宜,衡量需要什么程度的开放-封闭原则,这里提出两点:
1.挑选出最容易发生变化的地方,然后抽象封装这些变化
2.在需求的不到满足,不可避免的要发生修改代码时,尽量修改那些容易修改的地方。