本文通过汽车配置需求逐步引出JavaScript装饰者模式,对比修改接口、增加子类与使用装饰器三种方案,详细解析装饰者如何在不改变原有接口的前提下透明地扩展对象功能,并提供完整的代码实现与原理图解。
JavaScript装饰者模式概述
本文深入探讨JavaScript中装饰者模式的运作原理及其具体应用方式,旨在为读者提供清晰的参考与指导。
我们将通过一个具体的业务需求场景,逐步推导并引入装饰者模式的概念。
以下案例以不同代际汽车的配置差异为例,直观展示装饰者模式的应用过程。
首先,我们需要引入一个接口定义文件,其核心目的是在运行时严格校验实现类是否完整实现了接口中声明的所有方法。相关代码如下:
//定义一个静态方法来实现接口与实现类的直接检验
//静态方法不要写出Interface.prototype ,因为这是写到接口的原型链上的
//我们要把静态的函数直接写到类层次上
//定义一个接口类
var Interface=function (name,methods) {//name:接口名字
if(arguments.length<2){
alert("必须是两个参数")
}
this.name=name;
this.methods=[];//定义一个空数组装载函数名
for(var i=0;i<methods.length;i++){
if(typeof methods[i]!="string"){
alert("函数名必须是字符串类型");
}else {
this.methods.push( methods[i]);
}
}
};
Interface.ensureImplement=function (object) {
if(arguments.length<2){
throw new Error("参数必须不少于2个")
return false;
}
for(var i=1;i<arguments.length;i++){
var inter=arguments[i];
//如果是接口就必须是Interface类型
if(inter.constructor!=Interface){
throw new Error("如果是接口类的话,就必须是Interface类型");
}
//判断接口中的方法是否全部实现
//遍历函数集合分析
for(var j=0;j<inter.methods.length;j++){
var method=inter.methods[j];//接口中所有函数
//object[method]传入的函数
//最终是判断传入的函数是否与接口中所用函数匹配
if(!object[method]||typeof object[method]!="function" ){//实现类中必须有方法名字与接口中所用方法名相同
throw new Error("实现类中没有完全实现接口中的所有方法")
}
}
}
}
(1)统一接口定义
var ShopInterface=new Interface("FirstShop",["getPrice","assemble"]);//规定了实现的方法
(2)在实现接口的同时,于内部进行方法完整性校验
var first=function () {
//接口实现部分
this.getPrice=function () {
document.write(15000+"<br>")
}
this.assemble=function () {
document.write("汽车组装....<br>")
}
Interface.ensureImplement(this,ShopInterface);//检验类是否实现接口
}
(3)创建第一个汽车实例
//第一个汽车实例
var firstShop=new first();
firstShop.getPrice();
firstShop.assemble();
document.write("...............first...............<br>")
需求分析与方案对比
此时,业务提出新需求:汽车需支持附加配置,例如音响(K)、真皮沙发(M)及保险杠(N)。
分析可知,每一项附加配置都会影响汽车的组装流程及最终价格。面对这一需求,我们该如何设计解决方案?
方案一:直接修改原有接口定义
(1)将新配置方法加入接口定义
var SecondInterface=new Interface("SecondInterface",["getPrice","assemble","addK","addM","addN"]);
(2)修改类实现以包含新接口方法并进行校验
var second=function () {
var price=15000;
//实例接口部分
this.getPrice=function () {
document.write(price+"<br>")
}
this.assemble=function () {
document.write("汽车组装.....<br>");
}
this.addK=function () {
price+=1000;
}
this.addM=function () {
price+=2000;
}
this.addN=function () {
price+=3000;
}
Interface.ensureImplement(this,SecondInterface);//当前对象实例时会被调用
}
(3)基于新接口创建第二个汽车实例
//第二个汽车实例
var secondShop=new second();
secondShop.addK();
secondShop.addM();
secondShop.addN();
secondShop.getPrice();
secondShop.assemble();
document.write(".....................second.........................<br>");
虽然该方案看似可行,但存在严重缺陷:修改接口后,所有已实现该接口的类都必须随之修改,否则将导致兼容性问题。显然,要求修改所有现有实现类是不合理的。若坚持不改变接口,是否可以通过增加子类来解决?
方案二:保持接口不变,通过增加子类扩展功能
(1)接口定义保持不变
var thirdInterface=new Interface("FirstShop",["getPrice","assemble"]);
(2)定义汽车原型类,实现基础接口
var third=function () {
this.getPrice=function () {
document.write(15000+"<br>");
}
this.assemble=function () {
document.write("汽车组装.....<br>");
}
Interface.ensureImplement(this,thirdInterface);
}
(3)为每种配置组合创建对应的子类
var thirdM=function () {
this.getPrice=function () {
document.write(15000+"<br>");
}
this.assemble=function () {
document.write("汽车组装.....<br>");
}
Interface.ensureImplement(this,thirdInterface);
};
这种方案同样存在弊端:每增加一种配置组合,就需要编写一个新的子类。若配置组合繁多,子类数量将呈指数级增长,导致代码维护成本极高,因此该方案也不具备可行性。
装饰者模式实现与总结
方案三:采用装饰者模式(Decorator Pattern)
装饰者模式允许在不修改原有类代码的前提下,动态地为对象添加新的职责或特性。其核心机制是将原始对象透明地包装在具有相同接口的新对象中,从而形成一条功能链。
具体实现代码如下:
(1)接口定义保持不变,仅包含基础方法
var comInterface=new Interface("FirstShop",["getPrice","assemble"]);
(2)目标对象(即原型对象,被装饰的核心部分)实现接口,并在实例化时进行校验
var targetShop = function(){
this.getPrice = function(){
return 15000;
}
this.assemble =function(){
document.write("汽车组装....<br>")
}
Interface.ensureImplement(this,comInterface);//接口检验
}
(3)定义各个装饰类,用于包裹原始对象并扩展功能
#M(真皮沙发)装饰类:
var carM = function(carShop) {
this.getPrice = function () {
return 1000 + carShop.getPrice();
}
this.assemble = function () {
document.write("M组装....<br>")
}
Interface.ensureImplement(this,comInterface);//接口检验
}
#N(保险杠)装饰类:
var carN = function(carShop){
this.getPrice = function(){
return 2000+carShop.getPrice();
}
this.assemble =function(){
document.write("N组装....<br>")
}
Interface.ensureImplement(this,comInterface);//接口检验
}
#K(音响)装饰类:
var carK=function (carShop) {
this.getPrice=function () {
return 3000+carShop.getPrice();
}
this.assemble=function () {
document.write("K组装....<br>")
}
Interface.ensureImplement(this,comInterface);//接口检验
};
(4)通过组合不同的装饰类来包装我们的汽车对象
//包装车
var newCar=new carK(new carM(new targetShop));//有K和M的车
var newCar2=new carK(new carM(new carN(new targetShop)));
document.write(newCar.getPrice()+"<br>");
document.write(newCar2.getPrice());
总结而言,装饰者模式不仅适用于对类进行整体扩展,同样适用于对类中的特定函数进行增强。
当原有功能无法满足项目需求,且需要大量扩展功能,同时又不希望或无法修改原有接口时,装饰者模式是理想的选择。
为了更直观地理解上述模式,请参考以下图示:

包装原理图展示了装饰链的结构:

感兴趣的读者可以使用在线HTML/CSS/JavaScript前端代码调试运行工具:http://tools.jb51.net/code/WebCodeRun 来测试上述代码的运行效果。
如需了解更多JavaScript相关知识,可查阅本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》。
希望本文的阐述能对您的JavaScript程序设计工作有所帮助。