JS设计模式(二)

63 阅读7分钟

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 ); // 输出:开始渲染百度地图

参考:

  1. [JavaScript设计模式与开发实践]