JavaScript装饰者模式:通过汽车组装实例解析动态扩展功能

作者:纸飞机时间:2026-10-22 20:25:14浏览:323

本文通过汽车组装案例,深入解析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>");

看似实现了功能,但问题随之而来:如果修改了接口,所有实现该接口的类是否都必须包含K、M、N这些方法?显然,要求所有实现类都修改是不合理的。如果保持接口不变,是否可以通过增加子类来解决?

方案二:增加子类

方案二:保持接口不变,通过增加子类来实现。

(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);
};

我们会发现,如果子类数量众多,每个子类都要重复编写大量代码,这种方式显然不可取。

方案三:装饰者模式实现

方案三:采用装饰者模式。

装饰者模式允许为原型对象添加新特性,通过具有相同接口的新对象透明地包装原对象。

具体实现代码如下:

(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());

模式总结与应用场景

总结而言,装饰者模式既适用于类级别的扩展,也适用于对类中特定函数的增强。

当原有功能无法满足项目需求,且需要大量扩充功能,同时又不希望改变原有接口时,装饰者模式是理想的选择。

通过图示可以更直观地理解上述模式:

装饰者模式概述

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

装饰者模式概述

更多关于JavaScript的技术内容,可参考本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》。

希望本文的阐述能对您的JavaScript程序设计工作有所帮助。

作者最新文章