什么是装饰者模式
今天我们来讲另外一个非常实用的设计模式: 装饰者模式. 这个名字听上去有些莫名其妙, 不着急, 我们先来记住它的一个别名: 包装器模式.
我们记着这两个名字来开始今天的文章.
首先还是上设计模式一书中的经典定义:
动态地给一个对象添加一些额外的职责.
就增加功能来说, 装饰者模式相比生成子类更为灵活.
我们来分析一下这个定义.
给对象添加一些新的职责, 我们很容易想到创建子类来继承父类, 然后在子类上增加额外的职责.
那什么是动态地呢? 应该就是说这些新添加的职责在类一开始创建的时候我们并不知道, 而是在使用过程根据需要而添加的.
相比生成子类更为灵活, 这句话让装饰者模式和子类继承赤裸裸的刀兵相见了. 没有对比就没有伤害, 那我们就用例子来验证这句话.
传统面向对象的实现
我们假设你是以为已经走上人生巅峰的汽车生产商, 你的公司生产各种用途的汽车, 某一天一个客户下单了四种汽车, 分别是家用轿车, SUV, 旅行车和跑车. 我们很轻松地像下面这样进行交付了.
- var Car = function(){}
- Car.prototype.start = function(){
- console.log("轰轰轰, 启动正常!")
- }
- var Sedan = new car();// 小轿车
- var Suv = new Car();// SUV
- var Wagon=new Car();// 旅行车
- var Roadster=new Car();// 跑车
- // 是不是又学会了几个英文单词?
过了几天客户找来了, 说最近人们爱上了西藏自驾游, 人们都希望能够选装一些方便越野和载物的功能, 比如加装雪地胎, 行李箱, 升高底盘.
有经验的你满口答应下来, 这个简单, 于是你交付了下面的代码:
- //SUV
- Suv.prototype.changeTire = function(){
- console.log("我换了雪地胎");
- }
- Suv.prototype.addHeight = function(){
- console.log("我升高了底盘");
- }
- Suv.prototype.addBox = function(){
- console.log("我安装了行李箱");
- }
- //Wagon
- Wagon.prototype.changeTire = function(){
- console.log("我换了雪地胎");
- }
- Wagon.prototype.addHeight = function(){
- console.log("我升高了底盘");
- }
- Wagon.prototype.addBox = function(){
- console.log("我安装了行李箱");
- }
- //Sedan
- Sedan.prototype.changeTire = function(){
- console.log("我换了雪地胎");
- }
- Sedan.prototype.addHeight = function(){
- console.log("我升高了底盘");
- }
- Sedan.prototype.addBox = function(){
- console.log("我安装了行李箱");
- }
- // 使用
- var suv = new Suv();
- suv.changeTire();
- suv.addHeight();
- suv.addBox();
- suv.start();
- ...
你增加了多少种特性? 3x3=9 种.
你又问, 我直接把这三个特性加在 Car 上不行吗? 就不用这么麻烦了.
当然不行, 因为我们还有一种车: Roadster 跑车.
你能想象法拉利换了雪地胎背上行李箱升高底盘是个什么死样子吗? 这么干的人肯定疯了.
如果我们把特性一股脑加在 Car 上, 就避免不了这种情况的发生.
这个时候, 就体现出子类继承的不灵活之处.
下面, 装饰者模式就要正式登场了.
- var Car=function (){}
- Car.prototype.start=function(){
- console.log("轰轰轰, 启动正常!")
- }
- // 创建装饰类 (包装类)
- var ChangeTireDec=function(car){
- this.car=car;
- }
- var AddHeightDec=function(car){
- this.car=car;
- }
- var AddBoxDec=function(car){
- this.car=car;
- }
- // 装饰类具有和 Car 同样的特性, 只不过额外执行了一些其他的操作
- ChangeTireDec.prototype.start=function(){
- console.log("我换了雪地胎");
- this.car.start();
- }
- AddHeightDec.prototype.start=function(car){
- console.log("我升高了底盘");
- this.car.start();
- }
- AddBoxDec.prototype.start=function(car){
- console.log("我安装了行李箱");
- this.car.start();
- }
- // 使用
- var suv=new Suv();
- suv=new ChangeTireDec(suv);
- suv=new AddHeightDec(suv);
- suv=new AddBoxDec(suv);
- suv.start();
上面的代码你增加了几种特性? 只有三种! 而且不管你是给 SUV 还是 Wagon 还是 Sedan 加装, 都不需要再增加特性的代码.
这, 就是装饰者模式的优势所在.
现在我们再回过头来看看 GoF 的定义:
动态地给一个对象添加一些额外的职责.
就增加功能来说, 装饰者模式相比生成子类更为灵活.
怎么样, 是不是如同 1+1=2 一样简单了? 现在你应该也明白了为什么装饰者模式又叫座包装器模式了. 因为它将类的原有特性包装起来, 添加其他的特性, 就像一个箱子一样. 而且实现过程中, 还满足了封闭 - 开放原则.
JavaScript 的实现
上面的例子中, 我们是模拟了传统的面向对象语言来解释什么是装饰者模式. 我们都知道, 要动态改变 JavaScript 对象非常容易, 可以向操作变量一个操作对象, 我们再来改写下上面的例子, 让它更 javasripty
- var car = {
- start: function(){
- console.log("轰轰轰, 正常启动!");
- }
- }
- var ChangeTireDec = function(){
- console.log("我换了雪地胎");
- }
- var AddHeightDec = function(){
- console.log("我升高了底盘");
- }
- var AddBoxDec = function(){
- console.log("我安装了行李箱");
- }
- var start1 = car.start;
- car.start=function(){
- ChangeTireDec();
- start1();
- }
- var start2=car.start;
- car.start = function(){
- AddHeightDec();
- start2();
- }
- var start3=car.start();
- car.start = function(){
- AddBoxDec();
- start3();
- }
- // 执行
- car.start();
实际中的应用
从上面的例子我们可以看出来, 我们不断的将 car.start 的引用赋值给临时变量, 然后将原来的 car.start 指向新的对象 -- 包含了原来对象的引用和新的特性的对象. 这很好的保证了代码的
开放 - 封闭原则
, 这是今天第二次提到这个原则了, 就是对修改封闭, 对新增开放.
特别当你要重构一个非常复杂的多人项目时, 如果你不想因为修改了同事的一行代码而引起 "蝴蝶效应", 那么将他的方法整个打包赋值然后用装饰者模式增加新的功能, 是一种非常安全而且高效的做法.
来源: https://www.cnblogs.com/depsi/p/9102888.html