7.组合模式
组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构。 除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性
宏命令中包含了一组子命令,它们组成了一个树形结构,这里是一棵结构非常简单的树 。
在组合模式中,请求在树中传递的过程总是遵循一种逻辑:
请求从树最顶端的对象往下传递,如果当前处理请求的对象是叶对象(普通子命令),叶对象自身会对请求作出相应的处理;如果当前处理请求的对象是组合对象(宏命令), 组合对象则会遍历它属下的子节点,将请求继续传递给这些子节点。
请求从上到下沿着树进行传递,直到树的尽头。作为客户,只需要关心树最顶层的组合对象, 客户只需要请求这个组合对象,请求便会沿着树往下传递,依次到达所有的叶对象。
注:
1.组合模式不是父子关系:组合对象把请求委托给它所包含的所有叶对象,合作的关键是拥有相同的接口
2.对叶对象操作的一致性:组合模式除了拥有相同的接口,还有就是对一组叶对象的操作必须具有一致性
8.模版方法模式
\
定义:一种只需使用继承就可以实现的非常简单的模式。
组成:
1.抽象父类 :封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。
2.具体的实现子类:子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
模版方法模式是一种严重依赖抽象类的设计模式,而且基于继承。Js不支持抽象类,而且不支持真正的类式继承,继承是通过对象与对象之间的委托来实现的。
// 泡咖啡和泡茶的案例
// 饮料抽象父类
var Beverage = function( param ){
var boilWater = function(){ // 把水煮沸
console.log( '把水煮沸' );
};
var brew = param.brew || function(){ // 用沸水泡饮料
throw new Error( '必须传递 brew 方法' ); // 保证子类重写父类的抽象方法
};
var pourInCup = param.pourInCup || function(){ // 把饮料倒进杯子
throw new Error( '必须传递 pourInCup 方法' );
};
var addCondiments = param.addCondiments || function(){ // 添加调料
throw new Error( '必须传递 addCondiments 方法' );
};
// 构造器F
var F = function(){};
// 模版方法,封装子类的算法框架,指导子类顺序执行方法
F.prototype.init = function(){
boilWater();
brew();
pourInCup();
addCondiments();
};
return F;
};
// 咖啡子类继承重写
var Coffee = Beverage({
brew: function(){
console.log( '用沸水冲泡咖啡' );
},
pourInCup: function(){
console.log( '把咖啡倒进杯子' );
},
addCondiments: function(){
console.log( '加糖和牛奶' );
}
});
// 茶子类继承重写
var Tea = Beverage({
brew: function(){
console.log( '用沸水浸泡茶叶' );
},
pourInCup: function(){
console.log( '把茶倒进杯子' );
},
addCondiments: function(){
console.log( '加柠檬' );
}
});
var coffee = new Coffee();
coffee.init();
var tea = new Tea();
tea.init();
9.享元模式
作用:一种用于性能优化的模式。
核心:运用共享技术来有效支持大量细粒度的对象。
目标:尽量减少共享对象的数量。
过程:剥离外部状态,把外部状态保存在其他地方,在合适时刻把外部状态组装进共享对象。
关键:区分内部状态(可以被对象共享的属性)与外部状态(不能被共享)
特征:
1.内部状态存储于对象内部。
2.内部状态可以被一些对象共享。
3.内部状态独立于具体的场景,通常不会改变。
4.外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享
适用性:
1.一个程序中使用了大量的相似对象。
2.使用大量对象造成了很大的内存开销。
3.对象的大多数状态可以变为外部状态。
4.剥离外部状态后,可以用相对较少的共享对象取代大量对象。
外部状态在需要时传入共享对象,组装成一个完整对象,需要花费一定时间。所以,享元模式是一种用时间换空间的优化模式。
没有内部状态,但有外部状态的剥离,依然是享元模式。
没有外部状态的剥离,即使用了共享技术,也不是享元模式。
// 男女模特的案例:50件男装、50件女装
/*只需要区别男女模特,构造函数只接收 sex 参数*/
var Model = function( sex ){
this.sex = sex;
};
Model.prototype.takePhoto = function(){
console.log( 'sex= ' + this.sex + ' underwear=' + this.underwear);
};
/*分别创建一个男模特对象和一个女模特对象*/
var maleModel = new Model( 'male' ), // 内部状态
femaleModel = new Model( 'female' ); // 内部状态
/*给男模特依次穿上所有的男装,并进行拍照*/
for ( var i = 1; i <= 50; i++ ){ // 外部状态
maleModel.underwear = 'underwear' + i;
maleModel.takePhoto();
};
/*给女模特依次穿上所有的女装,并进行拍照*/
for ( var j = 1; j <= 50; j++ ){ // 外部状态
femaleModel.underwear = 'underwear' + j;
femaleModel.takePhoto();
};
//只需要两个对象便完成了同样的功能
10.职责链模式
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。这些对象称为链中的节点。
优点:解耦了请求发送者和 n 个接收者之间的复杂关系,只需把请求传递给第一个节点。可以手动指定起始节点,并不一定要从链中第一个节点开始传递。
缺点:不能保证请求一定会被链中的节点处理,可以在链尾增加一个保底的接受者节点来处理。另外,程序中多了一些节点对象,它们的作用可能仅仅是让请求传递下去,应避免职责链过长带来的性能损耗。
// 预付定金的案例
// orderType:订单类型。 pay:是否支付定金。 stock:库存数量
// 节点函数
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( '手机库存不足' );
}
};
// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
// 封装职责链
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( chainOrderNormal);
// 将请求传递给第一个节点
chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到 100 优惠券
chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到 50 优惠券
chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足
11.中介者模式
作用:解除对象与对象之间的紧耦合关系。使网状的多对多关系变成了相对简单的一对多关系。
缺点:中介者对象往往难以维护。
中介者对象实现方式:
1.利用发布-订阅模式。将中介者对象作为订阅者,其他对象作为发布者。
2.在中介者对象中开放一些接收消息的接口,其他对象可直接调用该接口给中介者发送消息。
12.装饰者模式
定义:给对象动态增加职责的方式。
作用:在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。
与代理模式区别:
代理模式:代理与本体之间的关系一开始就是确定的。一层引用。
装饰者模式:一开始不能确定对象的全部功能。一条装饰链。
// 原始对象
var plane = {
fire: function(){
console.log( '发射普通子弹' );
}
}
// 装饰对象
var missileDecorator = function(){
console.log( '发射导弹' );
}
var atomDecorator = function(){
console.log( '发射原子弹' );
}
// 动态添加职责
var fire1 = plane.fire;
plane.fire = function(){
fire1();
missileDecorator();
}
var fire2 = plane.fire;
plane.fire = function(){
fire2();
atomDecorator();
}
// 调用
plane.fire();
// 分别输出: 发射普通子弹、发射导弹、发射原子弹
13.状态模式
定义:允许一个对象在其内部状态改变时,改变它的行为。
关键:区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。
缺点:会定义许多状态类,增加不少对象,虽然避免条件分支语句,但逻辑分散。
与策略模式区别:
状态模式:状态和对应的行为早已封装好,状态间切换也早已完成,都发生在状态模式内部。
策略模式:各个策略类平等,没有任何联系,需要客户主动切换算法。
14.适配器模式
作用:解决两个软件实体间的接口不兼容的问题。
var googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' );
}
};
var baiduMap = {
display: function(){
console.log( '开始渲染百度地图' );
}
};
var baiduMapAdapter = {
show: function(){
return baiduMap.display();
}
};
renderMap( googleMap ); // 输出:开始渲染谷歌地图
renderMap( baiduMapAdapter ); // 输出:开始渲染百度地图
参考:
- [JavaScript设计模式与开发实践]