JavaScript设计模式(8)-装饰者模式

装饰者模式

1. 作用:

  • 可用来透明地把对象包装在具有同样接口的另一对象之中,这样可以给一个方法添加一些行为,然后将方法调用传递给原始对象。
  • 可用于为对象增加功能,用来代替大量子类。
  • 装饰者对其组件进行了透明包装,二者可以互换使用,因为他们 实现了同样的接口

2. 例子:自行车

function extend(subClass, superClass) {
    var F = function() {}
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass

    subClass.superclass = superClass.prototype
    if(superClass.prototype.constructor !== superClass) {
        superClass.prototype.constructor = superClass
    }
}

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

/**
* [AcmeComfortCruiser 自行车类]
*/
var AcmeComfortCruiser = function(){};
AcmeComfortCruiser.prototype = {
    assemble: function() {},
    wash: function() {},
    ride: function() {},
    repair: function() {},
    getPrice: function() {
        return 399.00
    }
}

/**
* [BicycleDecorator 装饰类的抽象类]
*/
var BicycleDecorator = function(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();
    }
}

/**
* [HeadlightDecorator 装饰者类]
*/
var HeadlightDecorator = function(bicycle) {
    HeadlightDecorator.superclass.constructor.call(this, bicycle)
}
extend(HeadlightDecorator, BicycleDecorator)
HeadlightDecorator.prototype.assemble = function() {
    return this.bicycle.assemble() + ' Attach headlight to handlebars.';
}
HeadlightDecorator.prototype.getPrice = function() {
    return this.bicycle.getPrice() + 15.00;
}

/**
* [TaillightDecorator 装饰者类]
*/
var TaillightDecorator = function(bicycle) {
    TaillightDecorator.superclass.constructor.call(this, bicycle)
}
extend(TaillightDecorator, BicycleDecorator);
TaillightDecorator.prototype.assemble = function() {
    return this.bicycle.assemble() + ' Attach taillight to the seat post.';
}
TaillightDecorator.prototype.getPrice = function() {
    return this.bicycle.getPrice() + 9.00;
}

// usage
var myBicycle = new AcmeComfortCruiser();
console.log(myBicycle.getPrice())

myBicycle = new HeadlightDecorator(myBicycle);
console.log(myBicycle.getPrice())

myBicycle = new TaillightDecorator(myBicycle);
console.log(myBicycle.getPrice())

3. 装饰者模式与组合模式的比较

  • 相同点:
    • 装饰者对象和组合对象都是用来包装别的对象(那些对象组合模式中称为子对象,装饰者模式中称为组件)
    • 实现了同样的接口,并且会把任何调用传递给这些对象
  • 差异点:
    • 组织模式:用于组织对象;装饰者模式:用于在不修改现有对象及不从其派生子类的前提下为其增添职责。
    • 装饰模式的子对象只有一个

4. 装饰者修改其组件的方式

4.1 在方法之后添加行为

先调用组件方法,并在其放回后实施一些附加行为

HeadlightDecorator.prototype.getPrice = function() {
    return this.bicycle.getPrice() + 15.00;
}

4.2 在方法之前添加行为

var FrameColorDecorator = function(bicycle, frameColor) {
    FrameColorDecorator.superclass.constructor.call(this, bicycle);

    // 添加了用以实现其提供的附加特性的属性
    this.frameColor = frameColor;
}
extend(FrameColorDecorator, BicycleDecorator);
FrameColorDecorator.prototype.assemble = function() {
    // 方法添加的步骤出现在其方法之前
    return 'Print the frame ' + this.frameColor + ' and allow it to dry. ' + this.bicycle.assemble()
}
FrameColorDecorator.prototype.getPrice = function() {
    return this.bicycle.getPrice() + 30.00;
}

// usage
var myBicycle = new AcmeComfortCruiser();
console.log(myBicycle.assemble())

myBicycle = new FrameColorDecorator(myBicycle, 'red')
console.log(myBicycle.assemble())

4.3 使用替代方法

引入替换组件方法的装饰者后,必须设法确保按正确的顺序应用装饰者(如:使用工厂方法,ps:参考第 5 大点)

/**
* [LifetimeWarrantyDecorator 装饰者类]
*/
var LifetimeWarrantyDecorator = function(bicycle) {
    LifetimeWarrantyDecorator.superclass.constructor.call(this, bicycle);
}
extend(LifetimeWarrantyDecorator, BicycleDecorator);

// 把原来的 repair 方法替换为一个新方法,而组件的方法则不会再被调用
LifetimeWarrantyDecorator.prototype.repair = function() {
    return 'This bicycle is covered by a lifetime warranty.'
}
LifetimeWarrantyDecorator.prototype.getPrice = function() {
    return this.bicycle.getPrice() + 199.00
}

/**
* [TimedWarrantyDecorator 装饰者类]
*/
var TimedWarrantyDecorator = function(bicycle, coverageLengthInYears) {
    TimedWarrantyDecorator.superclass.constructor.call(this, bicycle);
    this.coverageLength = coverageLengthInYears;
    this.expDate = new Date();
    var coverageLengthInMs = this.coverageLength * 365 * 24 * 60 * 60 * 1000;
    this.expDate.setTime(this.expDate.getTime() + coverageLengthInMs)
}
extend(TimedWarrantyDecorator, BicycleDecorator);

// 根据某种条件决定是否替代组件方法,在条件满足是替代,否则使用组件的方法
TimedWarrantyDecorator.prototype.repair = function() {
    var repairInstructions;
    var currentDate = new Date();
    if(currentDate < this.expDate) {
        repairInstructions = 'This bicycle is currently covered by a warrenty.'
    }else {
        repairInstructions = this.bicycle.repair();
    }
    return repairInstructions;
}
TimedWarrantyDecorator.prototype.getPrice = function() {
    return this.bicycle.getPrice() + (40.00 * this.coverageLength)
}

// usage
var myBicycle = new AcmeComfortCruiser();
console.log(myBicycle.getPrice())

// 替代
myBicycle = new LifetimeWarrantyDecorator(myBicycle)
console.log(myBicycle.repair())

// 判断是否替代
myBicycle = new TimedWarrantyDecorator(myBicycle, 1)
console.log(myBicycle.getPrice())

4.4 添加新方法

想稳妥地实现这一点并不容易,想要使用这些新方法,外围代码必须知道有这些新方法。由于这些新方法并不是在接口中定义的,而是动态添加的,因此有必要进行类型检查,以验明用于包装组件对象的最外层装饰者与用新方法装饰的组件对象相同

var BellDecorator = function(bicycle) {
    BellDecorator.superclass.constructor.call(this, bicycle);
}
extend(BellDecorator, BicycleDecorator);
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'
}

// usage
var myBicycle = new AcmeComfortCruiser();

myBicycle = new BellDecorator(myBicycle)
myBicycle = new HeadlightDecorator(myBicycle);

console.log(myBicycle.ringBell())  // 这样子会报错,因为 BellDecorator 添加的 ringBell 方法(及其他方法)会在 HeadlightDecorator 类通过 extend() 继承 new F() 时被抹除(也不是被抹除,只是不能在通过当前对象的原型链找到,其实这个方法在新对象的 bicycle 属性里面还是能通过其原型链找到)。

// BellDecorator 必须放在最后应用,否则这个新方法将无法访问。这是因为其他装饰者只能传递他们知道的方法,也即那些定义在接口中的方法。P170

关于上面说到的方法被抹除问题的解决方案

function extend(subClass, superClass) {
    var F = function() {}
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass

    subClass.superclass = superClass.prototype
    if(superClass.prototype.constructor !== superClass) {
        superClass.prototype.constructor = superClass
    }
}
var Interface = function(name, methods) {
    if(arguments.length !== 2) {
        throw new Error("Interface constructor called with "+ arguments.length + "arguments, but expected exactly 2")
    }
    this.name = name;
    this.methods = [];
    for(var i=0, len=methods.length; i<len; i++) {
        if(typeof methods[i] !== 'string') {
            throw new Error("Interface constructor expects method names to be passed in as a string")
        }
        this.methods.push(methods[i]);
    }
}  

var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair', 'getPrice']);

var BicycleDecorator = function(bicycle) {
    this.bicycle = bicycle;
    this.interface = Bicycle;
    outerloop:   // 使用标记,可以在程序的任何地方使用这个名字来引用他
    for(var key in this.bicycle) {
        if(typeof this.bicycle[key] !== 'function') {
            continue outerloop;
        }
        for(var i=0, len=this.interface.methods.length; i<len; i++) {
            if(key === this.interface.methods[i]) {
                continue outerloop
            }
        }
        var that = this;
        (function(methodName) {
            that[methodName] = function() {
                return that.bicycle[methodName]();
            }
        })(key);
    }
}
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();
    }
}

/**
* [AcmeComfortCruiser 自行车类]
*/
var AcmeComfortCruiser = function(){};
AcmeComfortCruiser.prototype = {
    assemble: function() {
        return 'assemble:'
    },
    wash: function() {},
    ride: function() {},
    repair: function() {},
    getPrice: function() {
        return 399.00
    }
}

/**
* [HeadlightDecorator 装饰者类]
*/
var HeadlightDecorator = function(bicycle) {
    HeadlightDecorator.superclass.constructor.call(this, bicycle)
}
extend(HeadlightDecorator, BicycleDecorator)
HeadlightDecorator.prototype.assemble = function() {
    return this.bicycle.assemble() + ' Attach headlight to handlebars.';
}
HeadlightDecorator.prototype.getPrice = function() {
    return this.bicycle.getPrice() + 15.00;
}

/**
* [BellDecorator 装饰者类]
*/
var BellDecorator = function(bicycle) {
    BellDecorator.superclass.constructor.call(this, bicycle);
}
extend(BellDecorator, BicycleDecorator);
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();
console.log(myBicycle.getPrice())

myBicycle = new BellDecorator(myBicycle)

myBicycle = new HeadlightDecorator(myBicycle);
console.log(myBicycle.getPrice())

console.log(myBicycle.ringBell())

5. 工厂的角色

可以使用工厂模式配合装饰者模式,这样就可以事先规定好实例化时的装饰者应用顺序,从而避免上面说到的新添加的方法在经过别的装饰类包装后访问不到添加的方法的问题


function extend(subClass, superClass) {
    var F = function() {}
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass

    subClass.superclass = superClass.prototype
    if(superClass.prototype.constructor !== superClass) {
        superClass.prototype.constructor = superClass
    }
}

var Interface = function(name, methods) {
    if(arguments.length !== 2) {
        throw new Error("Interface constructor called with "+ arguments.length + "arguments, but expected exactly 2")
    }
    this.name = name;
    this.methods = [];
    for(var i=0, len=methods.length; i<len; i++) {
        if(typeof methods[i] !== 'string') {
            throw new Error("Interface constructor expects method names to be passed in as a string")
        }
        this.methods.push(methods[i]);
    }
}  
Interface.ensureImplements = function(object) {
    if(arguments.length < 2) {
        throw new Error("Function Interface.ensureImplements call with " + arguments.length + "arguments, but expected at least 2")
    }
    for(var i=1,len=arguments.length; i<len; i++) {
        var interface = arguments[i];
        if(interface.constructor !== Interface) {
            throw new Error("Function Interface.ensureImplements expects arguments two and above to be instances of Interface");
        }
        for(var j=0, methodsLen = interface.methods.length; j<methodsLen; j++) {
            var method = interface.methods[j];
            if(!object[method] || typeof object[method] !== 'function') {
            throw new Error('Function Interface.ensureImplements: Object does not implement the '+ interface.name + " interface. Method " + method + " was not found")
            }
        }
    }
}

var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair', 'getPrice']);

// model 1
var AcmeComfortCruiser = function(){};
AcmeComfortCruiser.prototype = {
    assemble: function() {},
    wash: function() {},
    ride: function() {},
    repair: function() {},
    getPrice: function() {
        return 399.00
    }
}

// model 2
var AcmeSpeedster = function() {}
extend(AcmeSpeedster, AcmeComfortCruiser)

/**
* [BicycleDecorator 装饰类的抽象类]
*/
var BicycleDecorator = function(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();
    }
}

/**
* [HeadlightDecorator 装饰者类]
*/
var HeadlightDecorator = function(bicycle) {
    HeadlightDecorator.superclass.constructor.call(this, bicycle)
}
extend(HeadlightDecorator, BicycleDecorator)
HeadlightDecorator.prototype.assemble = function() {
    return this.bicycle.assemble() + ' Attach headlight to handlebars.';
}
HeadlightDecorator.prototype.getPrice = function() {
    return this.bicycle.getPrice() + 15.00;
}

/**
* [BellDecorator 装饰者类]
*/
var BellDecorator = function(bicycle) {
    BellDecorator.superclass.constructor.call(this, bicycle);
}
extend(BellDecorator, BicycleDecorator);
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'
}

// BicycleShop class 是一个抽象类,需要在继承后实现里面的方法
var BicycleShop = function() {};
BicycleShop.prototype = {
    sellBicycle: function(model) {
        var bicycle = this.createBicycle(model)
        bicycle.assemble()
    return bicycle;
    },
    // 工厂方法
    createBicycle: function(model) {
        throw new Error('Unsupported operation on an abstract class.')
    }
}

var AcmeBicycleShop = function() {};
extend(AcmeBicycleShop, BicycleShop);
AcmeBicycleShop.prototype.createBicycle = function(model, options) {
    var bicycle = new AcmeBicycleShop.models[model]();

    // 有必要时可以在这里对装饰者组件先后应用进行排序,下面使用的是直接遍历按顺序应用

    for(var i=0, len= options.length; i<len; i++) {
        var decorator = AcmeBicycleShop.options[options[i].name]
        if(typeof decorator !== 'function') {
            throw new Error('Decorator ' + options[i].name + 'not found');
        }
        var argument = options[i].arg;
        bicycle = new decorator(bicycle, argument)
    }
    
    Interface.ensureImplements(bicycle, Bicycle);
    return bicycle

}
AcmeBicycleShop.models = {
    'The Speedster' : AcmeSpeedster,
    'The Comfort Cruiser' : AcmeComfortCruiser
}
AcmeBicycleShop.options = {
    'headlight' : HeadlightDecorator,
    'bell': BellDecorator
}

var alecsCruisers = new AcmeBicycleShop();
var myBicycle = alecsCruisers.createBicycle('The Speedster', [
        {name: 'headlight'},
        {name: 'bell'}
    ])

myBicycle.ringBell()

6. 函数装饰者

用于包装独立的函数和方法的装饰者

6.1 包装函数

// 将传入函数的执行结果转化为大写形式
function upperCaseDecorator(func) {
    return function() {
        return func.apply(this, arguments).toUpperCase()
    }
}

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

getDateCaps = upperCaseDecorator(getDate)

// usage
getDateCaps()

6.2 包装方法

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

function extend(subClass, superClass) {
    var F = function() {}
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass

    subClass.superclass = superClass.prototype
    if(superClass.prototype.constructor !== superClass) {
        superClass.prototype.constructor = superClass
    }
}

/**
    * [AcmeComfortCruiser 自行车类]
    */
var AcmeComfortCruiser = function(){};
AcmeComfortCruiser.prototype = {
    assemble: function() {},
    wash: function() {},
    ride: function() {},
    repair: function() {},
    getPrice: function() {
        return 399.00
    }
}

/**
    * [BicycleDecorator 装饰类的抽象类]
    */
var BicycleDecorator = function(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();
    }
}

/**
    * [BellDecorator 装饰者类]
    */
var BellDecorator = function(bicycle) {
    BellDecorator.superclass.constructor.call(this, bicycle);
}
extend(BellDecorator, BicycleDecorator);

BellDecorator.prototype.ringBell = function() {
    return 'Bell rung'
}

// 使用函数装饰者装饰方法
BellDecorator.prototype.ringBellLoudly = upperCaseDecorator(BellDecorator.prototype.ringBell)

var myBicycle = new AcmeComfortCruiser();
myBicycle = new BellDecorator(myBicycle)
myBicycle.ringBell()
myBicycle.ringBellLoudly()

7. 装饰者模式的适用场合

  • 为类添加特效或职能,而从该类派生子类又不实际时(不实际可能是子类的数量大)
  • 需要为对象添加特效而又不想改变使用该对象的代码的话,也可使用装饰者模式。因为装饰者模式可以动态而透明的修改对象,所以它们很适合于修改现有系统这一任务。

8. 示例:方法性能分析器

/**
* [ListBuilder]
* @param {[type]} parent [description]
*/
var ListBuilder = function(parent) {
    this.parentEl = document.getElementById(parent);
}
ListBuilder.prototype = {
    buildList: function(listLength) {
        var list = document.createElement('ol');
        this.parentEl.appendChild(list);

        for(var i=0; i< listLength; i++) {
            var item = document.createElement('li');
            list.appendChild(item)
        }
    }
}

/**
* [MethodProfiler class]
* @param {[type]} component [description]
*/
var MethodProfiler = function(component) {
    this.component = component;
    this.timers = {};

    for(var key in this.component) {
        if(typeof this.component[key] !== 'function') {
            continue;
        }
        var that = this;
        // 使用匿名函数的作用是保留正确的 methodName 变量值
        (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')
    }
}

// usage
var list = new ListBuilder('feed-readers')
var listp = new MethodProfiler(list)
listp.buildList(500)

9. 装饰者的利与弊

  • 利:
    • 方便,灵活,透明
    • 不用重新定义对象就能对其进去扩充
  • 弊:
    • 在遇到用装饰者包装起来的对象时,那些依赖于类型检查的代码会出问题。
    • 使用装饰者模式往往会增加架构的复杂度。(添加了一些小对象;实现动态接口的装饰者涉及的语法细节也令人生畏)

注意

转载、引用,但请标明作者和原文地址

原文地址:https://www.cnblogs.com/CccZss/p/8492903.html