js 设计模式 第十二章 Decorator Pattern

来源:互联网 发布:淘宝xbox one手柄真假 编辑:程序博客网 时间:2024/06/06 08:34

why?

当我们想给类增加新功能时,我们可以通过派生类实现。但是用了Decorator Pattern ,我们不用派生各种新的类。

装饰器模式的适应场景:

1 需要为类增加新特性,但是为新特新子类化,不实际。

2 为函数添加功能,而不需要重写函数

how?

Decorator Pattern 引入了两个名词:

component ,即装饰类用来修饰的主体

装饰类对象可以代替component,因为他们都实现了同样的接口,这是Decorator Pattern 的关键。我们可以形象的思考,一个装饰类为什么要实现同component一样的接口?

当一个component 被修饰后,他还可以继续被其他的装饰修饰,so,被修饰的component应该还是component,这个通过实现同样的接口,能起到这样的目的,这是is-a的关系。

what?

以下是factory pattern 中,自行车店的例子。现在升级了,店面为自行车提供了非常多的零部件供安装,比如车头灯、车尾灯、车篮等。

var AcmeBicycleShop = function() {};extend(AcmeBicycleShop, BicycleShop);AcmeBicycleShop.prototype.createBicycle = function(model) {    var bicycle;    switch(model) {       case 'The Speedster':            bicycle = new AcmeSpeedster();            break;       case 'The Lowrider':            bicycle = new AcmeLowrider();            break;       case 'The Flatlander':            bicycle = new AcmeFlatlander();            break;        case 'The Comfort Cruiser':        default:            bicycle = new AcmeComfortCruiser();    }    Interface.ensureImplements(bicycle, Bicycle);    return bicycle;};

增加零部件,会改变车子的加个和组装流程。如何实现呢?最简单的解决方案是为每一种可能的零部件组合的自行车派生一个新类。

结果会这样:

var AcmeComfortCruiser = function() { ... }; // The superclass for all of the// other comfort cruisersvar AcmeComfortCruiserWithHeadlight = function() { ... };var AcmeComfortCruiserWithTaillight = function() { ... };var AcmeComfortCruiserWithHeadlightAndTaillight = function() { ... };var AcmeComfortCruiserWithBasket = function() { ... };var AcmeComfortCruiserWithHeadlightAndBasket = function() { ... };var AcmeComfortCruiserWithTaillightAndBasket = function() { ... };var AcmeComfortCruiserWithHeadlightTaillightAndBasket = function() { ... };var AcmeComfortCruiserWithBell = function() { ... };

产生数以百计的新类,你可不想花你剩下的半辈子写这些代码和维护这些代码吧

Decorator Pattern 是实现这个零部件的理想选择。用Decorator Pattern ,我们不用为自行车和零部件的每个组合新建类,我们仅仅为零部件创造几个类即可

注意事项:

用Decorator Pattern 创建的新类,它必须实现像其他自行车类一样实现Bicycle Interface的方法。

/* The Bicycle interface. */var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair','getPrice']);

为了给所有的装饰器提供一个默认的功能,我们写一个基类(非必须)

/* The BicycleDecorator abstract decorator class. */var BicycleDecorator = function(bicycle) { // implements Bicycle   Interface.ensureImplements(bicycle, Bicycle);   this.bicycle = bicycle;}BicycleDecorator.prototype = {    assemble: function() {       return this.bicycle.assemble();    },wash: function() {return this.bicycle.wash();},ride: function() {return this.bicycle.ride();},repair: function() {return this.bicycle.repair();},getPrice: function() {return this.bicycle.getPrice();}};

在构造函数中,接受一个自行车对象,作为component。基类实现了Bicycle Interface,每一个方法实现,简单的调用component 的对应方法

接下来,我们来创建具体的装饰类

/* HeadlightDecorator class. */var HeadlightDecorator = function(bicycle) { // implements Bicycle    this.superclass.constructor(bicycle); // Call the superclass's constructor.}extend(HeadlightDecorator, BicycleDecorator); // Extend the superclass.HeadlightDecorator.prototype.assemble = function() {    return this.bicycle.assemble() + ' Attach headlight to handlebars.';};HeadlightDecorator.prototype.getPrice = function() {    return this.bicycle.getPrice() + 15.00;};

车头灯装饰类,重写了assemble 和 getPrice 方法。两个方法都是先调用component 的对应方法,然后加上自己的一些特性。

有了车头灯装饰类,我们创建一个有车头灯的自行车,步骤如下:

1 实例化一个自行车

2 用1创建的自行车,实例化车头灯装饰类

ok,创建完毕,我们可以忘掉1中创建的自行车,把装饰类对待为自行车就对了

var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle.alert(myBicycle.getPrice()); // Returns 399.00myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object.alert(myBicycle.getPrice()); // Now returns 414.00


第三行非常重要,我们并没有单独为装饰类实例一个新的变量,而是同自行车放在同一个变量里面。因为我们不需要原来的自行车了,这是装饰类才是我们需要的有车头灯的自行车。


以下,我们再写一个尾灯的装饰类

/* TaillightDecorator class. */var TaillightDecorator = function(bicycle) { // implements Bicycle    this.superclass.constructor(bicycle); // Call the superclass's constructor.}extend(TaillightDecorator, BicycleDecorator); // Extend the superclass.TaillightDecorator.prototype.assemble = function() {    return this.bicycle.assemble() + ' Attach taillight to the seat post.';};TaillightDecorator.prototype.getPrice = function() {    return this.bicycle.getPrice() + 9.00;};

那么,我们可以结合几个装饰一起用了:

var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle.alert(myBicycle.getPrice()); // Returns 399.00myBicycle = new TaillightDecorator(myBicycle); // Decorate the bicycle object// with a taillight.alert(myBicycle.getPrice()); // Now returns 408.00myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object// again, now with a headlight.alert(myBicycle.getPrice()); // Now returns 423.00

我们知道,装饰类和component 都实现了同样的接口。但是,如果装饰类,想增加新的方法,怎么办?

/* BellDecorator class. */var BellDecorator = function(bicycle) { // implements Bicycle    this.superclass.constructor(bicycle); // Call the superclass's constructor.}extend(BellDecorator, BicycleDecorator); // Extend the superclass.BellDecorator.prototype.assemble = function() {    return this.bicycle.assemble() + ' Attach bell to handlebars.';};BellDecorator.prototype.getPrice = function() {    return this.bicycle.getPrice() + 6.00;};BellDecorator.prototype.ringBell = function() {    return 'Bell rung.';};

var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle.myBicycle = new BellDecorator(myBicycle); // Decorate the bicycle object// with a bell.alert(myBicycle.ringBell()); // Returns 'Bell rung.'

var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle.myBicycle = new BellDecorator(myBicycle); // Decorate the bicycle object// with a bell.myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object// with a headlight.alert(myBicycle.ringBell()); // Method not found.

可见,只用在最后再修饰ringBell时,才能调用ringBell函数。如果想顺序无关,怎么办呢?

我们可以改造BicycleDecortor 的构造函数,通过检测component的方法,来扩张装饰类的方法

/* The BicycleDecorator abstract decorator class, improved. */var BicycleDecorator = function(bicycle) { // implements Bicyclethis.bicycle = bicycle;this.interface = Bicycle;// Loop through all of the attributes of this.bicycle and create pass-through// methods for any methods that aren't currently implemented.outerloop: for(var key in this.bicycle) {// Ensure that the property is a function.   if(typeof this.bicycle[key] !== 'function') {        continue outerloop;   }// Ensure that the method isn't in the interface.   for(var i = 0, len = this.interface.methods.length; i < len; i++) {      if(key === this.interface.methods[i]) {            continue outerloop;      }    }// Add the new method.   var that = this;   (function(methodName) {       that[methodName] = function() {           return that.bicycle[methodName]();       };   })(key);  }}

这样,我们就可以在装饰类中添加特有的函数,再最后实例化得时候,也不用关心顺序的问题了


Decorator Pattern 不仅仅使用于类,它同样适用于函数

function Decorators

下面这个例子,装饰器包装了一个函数,让函数返回的结果都大写表示

function upperCaseDecorator(func){     return function(){         return func.apply(this,arguments).toUpperCase();     }}

比如有个函数:

function getDate() {    return (new Date()).toString();}

我们用上面的装饰器,来修饰这个函数:

getDateCaps = upperCaseDecorator(getDate);

然后,我们看看结果:

alert(getDate()); // Returns Wed Sep 26 2007 20:11:02 GMT-0700 (PDT)alert(getDateCaps()); // Returns WED SEP 26 2007 20:11:02 GMT-0700 (PDT)

最后,看一个method profiler 的例子,用来统计component中,各函数的执行时间

/* MethodProfiler class. */var MethodProfiler = function(component) {    this.component = component;    this.timers = {};    for(var key in this.component) {        // Ensure that the property is a function.       if(typeof this.component[key] !== 'function') {             continue;       }       // Add the method.       var that = this;(function(methodName) {    that[methodName] = function() {    that.startTimer(methodName);    var returnValue = that.component[methodName].apply(that.component,arguments);    that.displayTime(methodName, that.getElapsedTime(methodName));    return returnValue;    };})(key); }};MethodProfiler.prototype = {    startTimer: function(methodName) {        this.timers[methodName] = (new Date()).getTime();    },    getElapsedTime: function(methodName) {        return (new Date()).getTime() - this.timers[methodName];    },    displayTime: function(methodName, time) {         console.log(methodName + ': ' + time + ' ms');    }};

我们有这样一个类

var shape = function(){};shape.prototype.drawCircle = function(){};shape.prototype.drawRectangle = function(){};

可以利用上面的method profiler ,来统计shape 的各个方法的执行时间

var s = new shape();s = new Methodprofiler(s);s.drawCircle();//Displays "buildList: 287 ms".s.drawRectangle();//Displays "buildList: xxx ms".



原创粉丝点击